source: trunk/python/scantable.py @ 1731

Last change on this file since 1731 was 1731, checked in by Malte Marquarding, 14 years ago

Fixed wrong base class call in shift_refix

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 71.4 KB
Line 
1import os
2try:
3    from functools import wraps as wraps_dec
4except ImportError:
5    from asap.compatibility import wraps as wraps_dec
6
7from asap._asap import Scantable
8from asap import rcParams
9from asap import print_log, print_log_dec
10from asap import asaplog
11from asap import selector
12from asap import linecatalog
13from asap.coordinate import coordinate
14from asap import _n_bools, mask_not, mask_and, mask_or
15
16
17def preserve_selection(func):
18    @wraps_dec(func)
19    def wrap(obj, *args, **kw):
20        basesel = obj.get_selection()
21        val = func(obj, *args, **kw)
22        obj.set_selection(basesel)
23        return val
24    return wrap
25
26
27def is_scantable(filename):
28    return (os.path.isdir(filename)
29            and not os.path.exists(filename+'/table.f1')
30            and os.path.exists(filename+'/table.info'))
31
32
33class scantable(Scantable):
34    """
35        The ASAP container for scans
36    """
37    @print_log_dec
38    def __init__(self, filename, average=None, unit=None, parallactify=None):
39        """
40        Create a scantable from a saved one or make a reference
41        Parameters:
42            filename:    the name of an asap table on disk
43                         or
44                         the name of a rpfits/sdfits/ms file
45                         (integrations within scans are auto averaged
46                         and the whole file is read)
47                         or
48                         [advanced] a reference to an existing
49                         scantable
50            average:     average all integrations withinb a scan on read.
51                         The default (True) is taken from .asaprc.
52            unit:         brightness unit; must be consistent with K or Jy.
53                         Over-rides the default selected by the reader
54                         (input rpfits/sdfits/ms) or replaces the value
55                         in existing scantables
56            parallactify: Indcicate that the data had been parallatified.
57                          Default is taken form rc file.
58        """
59        if average is None:
60            average = rcParams['scantable.autoaverage']
61        parallactify = parallactify or rcParams['scantable.parallactify']
62        varlist = vars()
63        from asap._asap import stmath
64        self._math = stmath()
65        if isinstance(filename, Scantable):
66            Scantable.__init__(self, filename)
67        else:
68            if isinstance(filename, str):
69                filename = os.path.expandvars(filename)
70                filename = os.path.expanduser(filename)
71                if not os.path.exists(filename):
72                    s = "File '%s' not found." % (filename)
73                    if rcParams['verbose']:
74                        asaplog.push(s)
75                        return
76                    raise IOError(s)
77                if is_scantable(filename):
78                    ondisk = rcParams['scantable.storage'] == 'disk'
79                    Scantable.__init__(self, filename, ondisk)
80                    if unit is not None:
81                        self.set_fluxunit(unit)
82                    self.set_freqframe(rcParams['scantable.freqframe'])
83                else:
84                    self._fill([filename], unit, average)
85            elif (isinstance(filename, list) or isinstance(filename, tuple)) \
86                  and isinstance(filename[-1], str):
87                self._fill(filename, unit, average)
88        self.parallactify(parallactify)
89        self._add_history("scantable", varlist)
90
91    @print_log_dec
92    def save(self, name=None, format=None, overwrite=False):
93        """
94        Store the scantable on disk. This can be an asap (aips++) Table,
95        SDFITS or MS2 format.
96        Parameters:
97            name:        the name of the outputfile. For format "ASCII"
98                         this is the root file name (data in 'name'.txt
99                         and header in 'name'_header.txt)
100            format:      an optional file format. Default is ASAP.
101                         Allowed are - 'ASAP' (save as ASAP [aips++] Table),
102                                       'SDFITS' (save as SDFITS file)
103                                       'ASCII' (saves as ascii text file)
104                                       'MS2' (saves as an aips++
105                                              MeasurementSet V2)
106                                       'FITS' (save as image FITS - not
107                                               readable by class)
108                                       'CLASS' (save as FITS readable by CLASS)
109            overwrite:   If the file should be overwritten if it exists.
110                         The default False is to return with warning
111                         without writing the output. USE WITH CARE.
112        Example:
113            scan.save('myscan.asap')
114            scan.save('myscan.sdfits', 'SDFITS')
115        """
116        from os import path
117        format = format or rcParams['scantable.save']
118        suffix = '.'+format.lower()
119        if name is None or name == "":
120            name = 'scantable'+suffix
121            msg = "No filename given. Using default name %s..." % name
122            asaplog.push(msg)
123        name = path.expandvars(name)
124        if path.isfile(name) or path.isdir(name):
125            if not overwrite:
126                msg = "File %s exists." % name
127                if rcParams['verbose']:
128                    print msg
129                    return
130                else:
131                    raise IOError(msg)
132        format2 = format.upper()
133        if format2 == 'ASAP':
134            self._save(name)
135        else:
136            from asap._asap import stwriter as stw
137            writer = stw(format2)
138            writer.write(self, name)
139        return
140
141    def copy(self):
142        """
143        Return a copy of this scantable.
144        Note:
145            This makes a full (deep) copy. scan2 = scan1 makes a reference.
146        Parameters:
147            none
148        Example:
149            copiedscan = scan.copy()
150        """
151        sd = scantable(Scantable._copy(self))
152        return sd
153
154    def drop_scan(self, scanid=None):
155        """
156        Return a new scantable where the specified scan number(s) has(have)
157        been dropped.
158        Parameters:
159            scanid:    a (list of) scan number(s)
160        """
161        from asap import _is_sequence_or_number as _is_valid
162        from asap import _to_list
163        from asap import unique
164        if not _is_valid(scanid):
165            if rcParams['verbose']:
166                print "Please specify a scanno to drop from the scantable"
167                return
168            else:
169                raise RuntimeError("No scan given")
170        try:
171            scanid = _to_list(scanid)
172            allscans = unique([ self.getscan(i) for i in range(self.nrow())])
173            for sid in scanid: allscans.remove(sid)
174            if len(allscans) == 0:
175                raise ValueError("Can't remove all scans")
176        except ValueError:
177            if rcParams['verbose']:
178                print "Couldn't find any match."
179                return
180            else: raise
181        try:
182            sel = selector(scans=allscans)
183            return self._select_copy(sel)
184        except RuntimeError:
185            if rcParams['verbose']:
186                print "Couldn't find any match."
187            else:
188                raise
189
190    def _select_copy(self, selection):
191        orig = self.get_selection()
192        self.set_selection(orig+selection)
193        cp = self.copy()
194        self.set_selection(orig)
195        return cp
196
197    def get_scan(self, scanid=None):
198        """
199        Return a specific scan (by scanno) or collection of scans (by
200        source name) in a new scantable.
201        Note:
202            See scantable.drop_scan() for the inverse operation.
203        Parameters:
204            scanid:    a (list of) scanno or a source name, unix-style
205                       patterns are accepted for source name matching, e.g.
206                       '*_R' gets all 'ref scans
207        Example:
208            # get all scans containing the source '323p459'
209            newscan = scan.get_scan('323p459')
210            # get all 'off' scans
211            refscans = scan.get_scan('*_R')
212            # get a susbset of scans by scanno (as listed in scan.summary())
213            newscan = scan.get_scan([0, 2, 7, 10])
214        """
215        if scanid is None:
216            if rcParams['verbose']:
217                print "Please specify a scan no or name to " \
218                      "retrieve from the scantable"
219                return
220            else:
221                raise RuntimeError("No scan given")
222
223        try:
224            bsel = self.get_selection()
225            sel = selector()
226            if type(scanid) is str:
227                sel.set_name(scanid)
228                return self._select_copy(sel)
229            elif type(scanid) is int:
230                sel.set_scans([scanid])
231                return self._select_copy(sel)
232            elif type(scanid) is list:
233                sel.set_scans(scanid)
234                return self._select_copy(sel)
235            else:
236                msg = "Illegal scanid type, use 'int' or 'list' if ints."
237                if rcParams['verbose']:
238                    print msg
239                else:
240                    raise TypeError(msg)
241        except RuntimeError:
242            if rcParams['verbose']: print "Couldn't find any match."
243            else: raise
244
245    def __str__(self):
246        return Scantable._summary(self, True)
247
248    def summary(self, filename=None):
249        """
250        Print a summary of the contents of this scantable.
251        Parameters:
252            filename:    the name of a file to write the putput to
253                         Default - no file output
254        """
255        info = Scantable._summary(self, True)
256        if filename is not None:
257            if filename is "":
258                filename = 'scantable_summary.txt'
259            from os.path import expandvars, isdir
260            filename = expandvars(filename)
261            if not isdir(filename):
262                data = open(filename, 'w')
263                data.write(info)
264                data.close()
265            else:
266                msg = "Illegal file name '%s'." % (filename)
267                if rcParams['verbose']:
268                    print msg
269                else:
270                    raise IOError(msg)
271        if rcParams['verbose']:
272            try:
273                from IPython.genutils import page as pager
274            except ImportError:
275                from pydoc import pager
276            pager(info)
277        else:
278            return info
279
280    def get_spectrum(self, rowno):
281        """Return the spectrum for the current row in the scantable as a list.
282        Parameters:
283             rowno:   the row number to retrieve the spectrum from
284        """
285        return self._getspectrum(rowno)
286
287    def get_mask(self, rowno):
288        """Return the mask for the current row in the scantable as a list.
289        Parameters:
290             rowno:   the row number to retrieve the mask from
291        """
292        return self._getmask(rowno)
293
294    def set_spectrum(self, spec, rowno):
295        """Return the spectrum for the current row in the scantable as a list.
296        Parameters:
297             spec:   the spectrum
298             rowno:    the row number to set the spectrum for
299        """
300        assert(len(spec) == self.nchan())
301        return self._setspectrum(spec, rowno)
302
303    def get_coordinate(self, rowno):
304        """Return the (spectral) coordinate for a a given 'rowno'.
305        NOTE:
306            * This coordinate is only valid until a scantable method modifies
307              the frequency axis.
308            * This coordinate does contain the original frequency set-up
309              NOT the new frame. The conversions however are done using the user
310              specified frame (e.g. LSRK/TOPO). To get the 'real' coordinate,
311              use scantable.freq_align first. Without it there is no closure,
312              i.e.
313              c = myscan.get_coordinate(0)
314              c.to_frequency(c.get_reference_pixel()) != c.get_reference_value()
315
316        Parameters:
317             rowno:    the row number for the spectral coordinate
318
319        """
320        return coordinate(Scantable.get_coordinate(self, rowno))
321
322    def get_selection(self):
323        """
324        Get the selection object currently set on this scantable.
325        Parameters:
326            none
327        Example:
328            sel = scan.get_selection()
329            sel.set_ifs(0)              # select IF 0
330            scan.set_selection(sel)     # apply modified selection
331        """
332        return selector(self._getselection())
333
334    def set_selection(self, selection=None, **kw):
335        """
336        Select a subset of the data. All following operations on this scantable
337        are only applied to thi selection.
338        Parameters:
339            selection:    a selector object (default unset the selection),
340
341            or
342
343            any combination of
344            "pols", "ifs", "beams", "scans", "cycles", "name", "query"
345
346        Examples:
347            sel = selector()         # create a selection object
348            self.set_scans([0, 3])    # select SCANNO 0 and 3
349            scan.set_selection(sel)  # set the selection
350            scan.summary()           # will only print summary of scanno 0 an 3
351            scan.set_selection()     # unset the selection
352            # or the equivalent
353            scan.set_selection(scans=[0,3])
354            scan.summary()           # will only print summary of scanno 0 an 3
355            scan.set_selection()     # unset the selection
356        """
357        if selection is None:
358            # reset
359            if len(kw) == 0:
360                selection = selector()
361            else:
362                # try keywords
363                for k in kw:
364                    if k not in selector.fields:
365                        raise KeyError("Invalid selection key '%s', valid keys are %s" % (k, selector.fields))
366                selection = selector(**kw)
367        self._setselection(selection)
368
369    def stats(self, stat='stddev', mask=None):
370        """
371        Determine the specified statistic of the current beam/if/pol
372        Takes a 'mask' as an optional parameter to specify which
373        channels should be excluded.
374        Parameters:
375            stat:    'min', 'max', 'sumsq', 'sum', 'mean'
376                     'var', 'stddev', 'avdev', 'rms', 'median'
377            mask:    an optional mask specifying where the statistic
378                     should be determined.
379        Example:
380            scan.set_unit('channel')
381            msk = scan.create_mask([100, 200], [500, 600])
382            scan.stats(stat='mean', mask=m)
383        """
384        mask = mask or []
385        if not self._check_ifs():
386            raise ValueError("Cannot apply mask as the IFs have different "
387                             "number of channels. Please use setselection() "
388                             "to select individual IFs")
389
390        statvals = self._math._stats(self, mask, stat)
391        def cb(i):
392            return statvals[i]
393
394        return self._row_callback(cb, stat)
395
396    def stddev(self, mask=None):
397        """
398        Determine the standard deviation of the current beam/if/pol
399        Takes a 'mask' as an optional parameter to specify which
400        channels should be excluded.
401        Parameters:
402            mask:    an optional mask specifying where the standard
403                     deviation should be determined.
404
405        Example:
406            scan.set_unit('channel')
407            msk = scan.create_mask([100, 200], [500, 600])
408            scan.stddev(mask=m)
409        """
410        return self.stats(stat='stddev', mask=mask);
411
412
413    def get_column_names(self):
414        """
415        Return a  list of column names, which can be used for selection.
416        """
417        return list(Scantable.get_column_names(self))
418
419    def get_tsys(self, row=-1):
420        """
421        Return the System temperatures.
422        Returns:
423            a list of Tsys values for the current selection
424        """
425        if row > -1:
426            return self._get_column(self._gettsys, row)
427        return self._row_callback(self._gettsys, "Tsys")
428
429
430    def get_weather(self, row=-1):
431        values = self._get_column(self._get_weather, row)
432        if row > -1:
433            return {'temperature': values[0],
434                    'pressure': values[1], 'humidity' : values[2],
435                    'windspeed' : values[3], 'windaz' : values[4]
436                    }
437        else:
438            out = []
439            for r in values:
440
441                out.append({'temperature': r[0],
442                            'pressure': r[1], 'humidity' : r[2],
443                            'windspeed' : r[3], 'windaz' : r[4]
444                    })
445            return out
446
447    def _row_callback(self, callback, label):
448        out = ""
449        outvec = []
450        sep = '-'*50
451        for i in range(self.nrow()):
452            tm = self._gettime(i)
453            src = self._getsourcename(i)
454            out += 'Scan[%d] (%s) ' % (self.getscan(i), src)
455            out += 'Time[%s]:\n' % (tm)
456            if self.nbeam(-1) > 1:
457                out +=  ' Beam[%d] ' % (self.getbeam(i))
458            if self.nif(-1) > 1: out +=  ' IF[%d] ' % (self.getif(i))
459            if self.npol(-1) > 1: out +=  ' Pol[%d] ' % (self.getpol(i))
460            outvec.append(callback(i))
461            out += '= %3.3f\n' % (outvec[i])
462            out +=  sep+'\n'
463        if rcParams['verbose']:
464            print sep
465            print " %s" % (label)
466            print sep
467            print out
468        return outvec
469
470    def _get_column(self, callback, row=-1):
471        """
472        """
473        if row == -1:
474            return [callback(i) for i in range(self.nrow())]
475        else:
476            if 0 <= row < self.nrow():
477                return callback(row)
478
479
480    def get_time(self, row=-1, asdatetime=False):
481        """
482        Get a list of time stamps for the observations.
483        Return a datetime object for each integration time stamp in the scantable.
484        Parameters:
485            row:          row no of integration. Default -1 return all rows
486            asdatetime:   return values as datetime objects rather than strings
487        Example:
488            none
489        """
490        from time import strptime
491        from datetime import datetime
492        times = self._get_column(self._gettime, row)
493        if not asdatetime:
494            return times
495        format = "%Y/%m/%d/%H:%M:%S"
496        if isinstance(times, list):
497            return [datetime(*strptime(i, format)[:6]) for i in times]
498        else:
499            return datetime(*strptime(times, format)[:6])
500
501
502    def get_inttime(self, row=-1):
503        """
504        Get a list of integration times for the observations.
505        Return a time in seconds for each integration in the scantable.
506        Parameters:
507            row:    row no of integration. Default -1 return all rows.
508        Example:
509            none
510        """
511        return self._get_column(self._getinttime, row)
512
513
514    def get_sourcename(self, row=-1):
515        """
516        Get a list source names for the observations.
517        Return a string for each integration in the scantable.
518        Parameters:
519            row:    row no of integration. Default -1 return all rows.
520        Example:
521            none
522        """
523        return self._get_column(self._getsourcename, row)
524
525    def get_elevation(self, row=-1):
526        """
527        Get a list of elevations for the observations.
528        Return a float for each integration in the scantable.
529        Parameters:
530            row:    row no of integration. Default -1 return all rows.
531        Example:
532            none
533        """
534        return self._get_column(self._getelevation, row)
535
536    def get_azimuth(self, row=-1):
537        """
538        Get a list of azimuths for the observations.
539        Return a float for each integration in the scantable.
540        Parameters:
541            row:    row no of integration. Default -1 return all rows.
542        Example:
543            none
544        """
545        return self._get_column(self._getazimuth, row)
546
547    def get_parangle(self, row=-1):
548        """
549        Get a list of parallactic angles for the observations.
550        Return a float for each integration in the scantable.
551        Parameters:
552            row:    row no of integration. Default -1 return all rows.
553        Example:
554            none
555        """
556        return self._get_column(self._getparangle, row)
557
558    def get_direction(self, row=-1):
559        """
560        Get a list of Positions on the sky (direction) for the observations.
561        Return a string for each integration in the scantable.
562        Parameters:
563            row:    row no of integration. Default -1 return all rows
564        Example:
565            none
566        """
567        return self._get_column(self._getdirection, row)
568
569    def get_directionval(self, row=-1):
570        """
571        Get a list of Positions on the sky (direction) for the observations.
572        Return a float for each integration in the scantable.
573        Parameters:
574            row:    row no of integration. Default -1 return all rows
575        Example:
576            none
577        """
578        return self._get_column(self._getdirectionvec, row)
579
580    @print_log_dec
581    def set_unit(self, unit='channel'):
582        """
583        Set the unit for all following operations on this scantable
584        Parameters:
585            unit:    optional unit, default is 'channel'
586                     one of '*Hz', 'km/s', 'channel', ''
587        """
588        varlist = vars()
589        if unit in ['', 'pixel', 'channel']:
590            unit = ''
591        inf = list(self._getcoordinfo())
592        inf[0] = unit
593        self._setcoordinfo(inf)
594        self._add_history("set_unit", varlist)
595
596    @print_log_dec
597    def set_instrument(self, instr):
598        """
599        Set the instrument for subsequent processing.
600        Parameters:
601            instr:    Select from 'ATPKSMB', 'ATPKSHOH', 'ATMOPRA',
602                      'DSS-43' (Tid), 'CEDUNA', and 'HOBART'
603        """
604        self._setInstrument(instr)
605        self._add_history("set_instument", vars())
606
607    @print_log_dec
608    def set_feedtype(self, feedtype):
609        """
610        Overwrite the feed type, which might not be set correctly.
611        Parameters:
612            feedtype:     'linear' or 'circular'
613        """
614        self._setfeedtype(feedtype)
615        self._add_history("set_feedtype", vars())
616
617    @print_log_dec
618    def set_doppler(self, doppler='RADIO'):
619        """
620        Set the doppler for all following operations on this scantable.
621        Parameters:
622            doppler:    One of 'RADIO', 'OPTICAL', 'Z', 'BETA', 'GAMMA'
623        """
624        varlist = vars()
625        inf = list(self._getcoordinfo())
626        inf[2] = doppler
627        self._setcoordinfo(inf)
628        self._add_history("set_doppler", vars())
629
630    @print_log_dec
631    def set_freqframe(self, frame=None):
632        """
633        Set the frame type of the Spectral Axis.
634        Parameters:
635            frame:   an optional frame type, default 'LSRK'. Valid frames are:
636                     'REST', 'TOPO', 'LSRD', 'LSRK', 'BARY',
637                     'GEO', 'GALACTO', 'LGROUP', 'CMB'
638        Examples:
639            scan.set_freqframe('BARY')
640        """
641        frame = frame or rcParams['scantable.freqframe']
642        varlist = vars()
643        valid = ['REST', 'TOPO', 'LSRD', 'LSRK', 'BARY', \
644                   'GEO', 'GALACTO', 'LGROUP', 'CMB']
645
646        if frame in valid:
647            inf = list(self._getcoordinfo())
648            inf[1] = frame
649            self._setcoordinfo(inf)
650            self._add_history("set_freqframe", varlist)
651        else:
652            msg  = "Please specify a valid freq type. Valid types are:\n", valid
653            if rcParams['verbose']:
654                print msg
655            else:
656                raise TypeError(msg)
657
658    def set_dirframe(self, frame=""):
659        """
660        Set the frame type of the Direction on the sky.
661        Parameters:
662            frame:   an optional frame type, default ''. Valid frames are:
663                     'J2000', 'B1950', 'GALACTIC'
664        Examples:
665            scan.set_dirframe('GALACTIC')
666        """
667        varlist = vars()
668        try:
669            Scantable.set_dirframe(self, frame)
670        except RuntimeError, msg:
671            if rcParams['verbose']:
672                print msg
673            else:
674                raise
675        self._add_history("set_dirframe", varlist)
676
677    def get_unit(self):
678        """
679        Get the default unit set in this scantable
680        Returns:
681            A unit string
682        """
683        inf = self._getcoordinfo()
684        unit = inf[0]
685        if unit == '': unit = 'channel'
686        return unit
687
688    def get_abcissa(self, rowno=0):
689        """
690        Get the abcissa in the current coordinate setup for the currently
691        selected Beam/IF/Pol
692        Parameters:
693            rowno:    an optional row number in the scantable. Default is the
694                      first row, i.e. rowno=0
695        Returns:
696            The abcissa values and the format string (as a dictionary)
697        """
698        abc = self._getabcissa(rowno)
699        lbl = self._getabcissalabel(rowno)
700        return abc, lbl
701
702    def flag(self, mask=None):
703        """
704        Flag the selected data using an optional channel mask.
705        Parameters:
706            mask:   an optional channel mask, created with create_mask. Default
707                    (no mask) is all channels.
708        """
709        varlist = vars()
710        mask = mask or []
711        try:
712            self._flag(mask)
713        except RuntimeError, msg:
714            if rcParams['verbose']:
715                print msg
716                return
717            else: raise
718        self._add_history("flag", varlist)
719
720    @print_log_dec
721    def lag_flag(self, start, end, unit="MHz", insitu=None):
722        """
723        Flag the data in 'lag' space by providing a frequency to remove.
724        Flagged data in the scantable gets interpolated over the region.
725        No taper is applied.
726        Parameters:
727            start:    the start frequency (really a period within the
728                      bandwidth)  or period to remove
729            end:      the end frequency or period to remove
730            unit:     the frequency unit (default "MHz") or "" for
731                      explicit lag channels
732        Notes:
733            It is recommended to flag edges of the band or strong
734            signals beforehand.
735        """
736        if insitu is None: insitu = rcParams['insitu']
737        self._math._setinsitu(insitu)
738        varlist = vars()
739        base = { "GHz": 1000000000., "MHz": 1000000., "kHz": 1000., "Hz": 1.}
740        if not (unit == "" or base.has_key(unit)):
741            raise ValueError("%s is not a valid unit." % unit)
742        try:
743            if unit == "":
744                s = scantable(self._math._lag_flag(self, start, end, "lags"))
745            else:
746                s = scantable(self._math._lag_flag(self, start*base[unit],
747                                                   end*base[unit], "frequency"))
748        except RuntimeError, msg:
749            if rcParams['verbose']:
750                print msg
751                return
752            else: raise
753        s._add_history("lag_flag", varlist)
754        if insitu:
755            self._assign(s)
756        else:
757            return s
758
759    @print_log_dec
760    def create_mask(self, *args, **kwargs):
761        """
762        Compute and return a mask based on [min, max] windows.
763        The specified windows are to be INCLUDED, when the mask is
764        applied.
765        Parameters:
766            [min, max], [min2, max2], ...
767                Pairs of start/end points (inclusive)specifying the regions
768                to be masked
769            invert:     optional argument. If specified as True,
770                        return an inverted mask, i.e. the regions
771                        specified are EXCLUDED
772            row:        create the mask using the specified row for
773                        unit conversions, default is row=0
774                        only necessary if frequency varies over rows.
775        Example:
776            scan.set_unit('channel')
777            a)
778            msk = scan.create_mask([400, 500], [800, 900])
779            # masks everything outside 400 and 500
780            # and 800 and 900 in the unit 'channel'
781
782            b)
783            msk = scan.create_mask([400, 500], [800, 900], invert=True)
784            # masks the regions between 400 and 500
785            # and 800 and 900 in the unit 'channel'
786            c)
787            mask only channel 400
788            msk =  scan.create_mask([400])
789        """
790        row = kwargs.get("row", 0)
791        data = self._getabcissa(row)
792        u = self._getcoordinfo()[0]
793        if rcParams['verbose']:
794            if u == "": u = "channel"
795            msg = "The current mask window unit is %s" % u
796            i = self._check_ifs()
797            if not i:
798                msg += "\nThis mask is only valid for IF=%d" % (self.getif(i))
799            asaplog.push(msg)
800        n = self.nchan()
801        msk = _n_bools(n, False)
802        # test if args is a 'list' or a 'normal *args - UGLY!!!
803
804        ws = (isinstance(args[-1][-1], int) or isinstance(args[-1][-1], float)) \
805             and args or args[0]
806        for window in ws:
807            if len(window) == 1:
808                window = [window[0], window[0]]
809            if len(window) == 0 or  len(window) > 2:
810                raise ValueError("A window needs to be defined as [start(, end)]")
811            if window[0] > window[1]:
812                tmp = window[0]
813                window[0] = window[1]
814                window[1] = tmp
815            for i in range(n):
816                if data[i] >= window[0] and data[i] <= window[1]:
817                    msk[i] = True
818        if kwargs.has_key('invert'):
819            if kwargs.get('invert'):
820                msk = mask_not(msk)
821        return msk
822
823    def get_restfreqs(self):
824        """
825        Get the restfrequency(s) stored in this scantable.
826        The return value(s) are always of unit 'Hz'
827        Parameters:
828            none
829        Returns:
830            a list of doubles
831        """
832        return list(self._getrestfreqs())
833
834
835    def set_restfreqs(self, freqs=None, unit='Hz'):
836        """
837        Set or replace the restfrequency specified and
838        If the 'freqs' argument holds a scalar,
839        then that rest frequency will be applied to all the selected
840        data.  If the 'freqs' argument holds
841        a vector, then it MUST be of equal or smaller length than
842        the number of IFs (and the available restfrequencies will be
843        replaced by this vector).  In this case, *all* data have
844        the restfrequency set per IF according
845        to the corresponding value you give in the 'freqs' vector.
846        E.g. 'freqs=[1e9, 2e9]'  would mean IF 0 gets restfreq 1e9 and
847        IF 1 gets restfreq 2e9.
848        You can also specify the frequencies via a linecatalog.
849
850        Parameters:
851            freqs:   list of rest frequency values or string idenitfiers
852            unit:    unit for rest frequency (default 'Hz')
853
854        Example:
855            # set the given restfrequency for the whole table
856            scan.set_restfreqs(freqs=1.4e9)
857            # If thee number of IFs in the data is >= 2 IF0 gets the first
858            # value IF1 the second...
859            scan.set_restfreqs(freqs=[1.4e9, 1.67e9])
860            #set the given restfrequency for the whole table (by name)
861            scan.set_restfreqs(freqs="OH1667")
862
863        Note:
864            To do more sophisticate Restfrequency setting, e.g. on a
865            source and IF basis, use scantable.set_selection() before using
866            this function.
867            # provide your scantable is called scan
868            selection = selector()
869            selection.set_name("ORION*")
870            selection.set_ifs([1])
871            scan.set_selection(selection)
872            scan.set_restfreqs(freqs=86.6e9)
873
874        """
875        varlist = vars()
876        from asap import linecatalog
877        # simple  value
878        if isinstance(freqs, int) or isinstance(freqs, float):
879            self._setrestfreqs(freqs, "",unit)
880        # list of values
881        elif isinstance(freqs, list) or isinstance(freqs, tuple):
882            # list values are scalars
883            if isinstance(freqs[-1], int) or isinstance(freqs[-1], float):
884                sel = selector()
885                savesel = self._getselection()
886                iflist = self.getifnos()
887                for i in xrange(len(freqs)):
888                    sel.set_ifs(iflist[i])
889                    self._setselection(sel)
890                    self._setrestfreqs(freqs[i], "",unit)
891                self._setselection(savesel)
892            # list values are tuples, (value, name)
893            elif isinstance(freqs[-1], dict):
894                sel = selector()
895                savesel = self._getselection()
896                iflist = self.getifnos()
897                for i in xrange(len(freqs)):
898                    sel.set_ifs(iflist[i])
899                    self._setselection(sel)
900                    self._setrestfreqs(freqs[i]["value"],
901                                       freqs[i]["name"], "MHz")
902                self._setselection(savesel)
903        # freqs are to be taken from a linecatalog
904        elif isinstance(freqs, linecatalog):
905            sel = selector()
906            savesel = self._getselection()
907            iflist = self.getifnos()
908            for i in xrange(freqs.nrow()):
909                sel.set_ifs(iflist[i])
910                self._setselection(sel)
911                self._setrestfreqs(freqs.get_frequency(i),
912                                   freqs.get_name(i), "MHz")
913                # ensure that we are not iterating past nIF
914                if i == self.nif()-1: break
915            self._setselection(savesel)
916        else:
917            return
918        self._add_history("set_restfreqs", varlist)
919
920    def shift_refpix(self, delta):
921        """
922        Shift the reference pixel of the Spectra Coordinate by an
923        integer amount.
924        Parameters:
925            delta:   the amount to shift by
926        Note:
927            Be careful using this with broadband data.
928        """
929        Scantable.shift_refpix(self, delta)
930
931    def history(self, filename=None):
932        """
933        Print the history. Optionally to a file.
934        Parameters:
935            filename:    The name  of the file to save the history to.
936        """
937        hist = list(self._gethistory())
938        out = "-"*80
939        for h in hist:
940            if h.startswith("---"):
941                out += "\n"+h
942            else:
943                items = h.split("##")
944                date = items[0]
945                func = items[1]
946                items = items[2:]
947                out += "\n"+date+"\n"
948                out += "Function: %s\n  Parameters:" % (func)
949                for i in items:
950                    s = i.split("=")
951                    out += "\n   %s = %s" % (s[0], s[1])
952                out += "\n"+"-"*80
953        if filename is not None:
954            if filename is "":
955                filename = 'scantable_history.txt'
956            import os
957            filename = os.path.expandvars(os.path.expanduser(filename))
958            if not os.path.isdir(filename):
959                data = open(filename, 'w')
960                data.write(out)
961                data.close()
962            else:
963                msg = "Illegal file name '%s'." % (filename)
964                if rcParams['verbose']:
965                    print msg
966                else:
967                    raise IOError(msg)
968        if rcParams['verbose']:
969            try:
970                from IPython.genutils import page as pager
971            except ImportError:
972                from pydoc import pager
973            pager(out)
974        else:
975            return out
976        return
977    #
978    # Maths business
979    #
980    @print_log_dec
981    def average_time(self, mask=None, scanav=False, weight='tint', align=False):
982        """
983        Return the (time) weighted average of a scan.
984        Note:
985            in channels only - align if necessary
986        Parameters:
987            mask:     an optional mask (only used for 'var' and 'tsys'
988                      weighting)
989            scanav:   True averages each scan separately
990                      False (default) averages all scans together,
991            weight:   Weighting scheme.
992                      'none'     (mean no weight)
993                      'var'      (1/var(spec) weighted)
994                      'tsys'     (1/Tsys**2 weighted)
995                      'tint'     (integration time weighted)
996                      'tintsys'  (Tint/Tsys**2)
997                      'median'   ( median averaging)
998                      The default is 'tint'
999            align:    align the spectra in velocity before averaging. It takes
1000                      the time of the first spectrum as reference time.
1001        Example:
1002            # time average the scantable without using a mask
1003            newscan = scan.average_time()
1004        """
1005        varlist = vars()
1006        weight = weight or 'TINT'
1007        mask = mask or ()
1008        scanav = (scanav and 'SCAN') or 'NONE'
1009        scan = (self, )
1010        try:
1011            if align:
1012                scan = (self.freq_align(insitu=False), )
1013            s = None
1014            if weight.upper() == 'MEDIAN':
1015                s = scantable(self._math._averagechannel(scan[0], 'MEDIAN',
1016                                                         scanav))
1017            else:
1018                s = scantable(self._math._average(scan, mask, weight.upper(),
1019                              scanav))
1020        except RuntimeError, msg:
1021            if rcParams['verbose']:
1022                print msg
1023                return
1024            else: raise
1025        s._add_history("average_time", varlist)
1026        return s
1027
1028    @print_log_dec
1029    def convert_flux(self, jyperk=None, eta=None, d=None, insitu=None):
1030        """
1031        Return a scan where all spectra are converted to either
1032        Jansky or Kelvin depending upon the flux units of the scan table.
1033        By default the function tries to look the values up internally.
1034        If it can't find them (or if you want to over-ride), you must
1035        specify EITHER jyperk OR eta (and D which it will try to look up
1036        also if you don't set it). jyperk takes precedence if you set both.
1037        Parameters:
1038            jyperk:      the Jy / K conversion factor
1039            eta:         the aperture efficiency
1040            d:           the geomtric diameter (metres)
1041            insitu:      if False a new scantable is returned.
1042                         Otherwise, the scaling is done in-situ
1043                         The default is taken from .asaprc (False)
1044        """
1045        if insitu is None: insitu = rcParams['insitu']
1046        self._math._setinsitu(insitu)
1047        varlist = vars()
1048        jyperk = jyperk or -1.0
1049        d = d or -1.0
1050        eta = eta or -1.0
1051        s = scantable(self._math._convertflux(self, d, eta, jyperk))
1052        s._add_history("convert_flux", varlist)
1053        if insitu: self._assign(s)
1054        else: return s
1055
1056    @print_log_dec
1057    def gain_el(self, poly=None, filename="", method="linear", insitu=None):
1058        """
1059        Return a scan after applying a gain-elevation correction.
1060        The correction can be made via either a polynomial or a
1061        table-based interpolation (and extrapolation if necessary).
1062        You specify polynomial coefficients, an ascii table or neither.
1063        If you specify neither, then a polynomial correction will be made
1064        with built in coefficients known for certain telescopes (an error
1065        will occur if the instrument is not known).
1066        The data and Tsys are *divided* by the scaling factors.
1067        Parameters:
1068            poly:        Polynomial coefficients (default None) to compute a
1069                         gain-elevation correction as a function of
1070                         elevation (in degrees).
1071            filename:    The name of an ascii file holding correction factors.
1072                         The first row of the ascii file must give the column
1073                         names and these MUST include columns
1074                         "ELEVATION" (degrees) and "FACTOR" (multiply data
1075                         by this) somewhere.
1076                         The second row must give the data type of the
1077                         column. Use 'R' for Real and 'I' for Integer.
1078                         An example file would be
1079                         (actual factors are arbitrary) :
1080
1081                         TIME ELEVATION FACTOR
1082                         R R R
1083                         0.1 0 0.8
1084                         0.2 20 0.85
1085                         0.3 40 0.9
1086                         0.4 60 0.85
1087                         0.5 80 0.8
1088                         0.6 90 0.75
1089            method:      Interpolation method when correcting from a table.
1090                         Values are  "nearest", "linear" (default), "cubic"
1091                         and "spline"
1092            insitu:      if False a new scantable is returned.
1093                         Otherwise, the scaling is done in-situ
1094                         The default is taken from .asaprc (False)
1095        """
1096
1097        if insitu is None: insitu = rcParams['insitu']
1098        self._math._setinsitu(insitu)
1099        varlist = vars()
1100        poly = poly or ()
1101        from os.path import expandvars
1102        filename = expandvars(filename)
1103        s = scantable(self._math._gainel(self, poly, filename, method))
1104        s._add_history("gain_el", varlist)
1105        if insitu:
1106            self._assign(s)
1107        else:
1108            return s
1109
1110    @print_log_dec
1111    def freq_align(self, reftime=None, method='cubic', insitu=None):
1112        """
1113        Return a scan where all rows have been aligned in frequency/velocity.
1114        The alignment frequency frame (e.g. LSRK) is that set by function
1115        set_freqframe.
1116        Parameters:
1117            reftime:     reference time to align at. By default, the time of
1118                         the first row of data is used.
1119            method:      Interpolation method for regridding the spectra.
1120                         Choose from "nearest", "linear", "cubic" (default)
1121                         and "spline"
1122            insitu:      if False a new scantable is returned.
1123                         Otherwise, the scaling is done in-situ
1124                         The default is taken from .asaprc (False)
1125        """
1126        if insitu is None: insitu = rcParams["insitu"]
1127        self._math._setinsitu(insitu)
1128        varlist = vars()
1129        reftime = reftime or ""
1130        s = scantable(self._math._freq_align(self, reftime, method))
1131        s._add_history("freq_align", varlist)
1132        if insitu: self._assign(s)
1133        else: return s
1134
1135    @print_log_dec
1136    def opacity(self, tau=None, insitu=None):
1137        """
1138        Apply an opacity correction. The data
1139        and Tsys are multiplied by the correction factor.
1140        Parameters:
1141            tau:         (list of) opacity from which the correction factor is
1142                         exp(tau*ZD)
1143                         where ZD is the zenith-distance.
1144                         If a list is provided, it has to be of length nIF,
1145                         nIF*nPol or 1 and in order of IF/POL, e.g.
1146                         [opif0pol0, opif0pol1, opif1pol0 ...]
1147                         if tau is `None` the opacities are determined from a
1148                         model.
1149            insitu:      if False a new scantable is returned.
1150                         Otherwise, the scaling is done in-situ
1151                         The default is taken from .asaprc (False)
1152        """
1153        if insitu is None: insitu = rcParams['insitu']
1154        self._math._setinsitu(insitu)
1155        varlist = vars()
1156        if not hasattr(tau, "__len__"):
1157            tau = [tau]
1158        s = scantable(self._math._opacity(self, tau))
1159        s._add_history("opacity", varlist)
1160        if insitu: self._assign(s)
1161        else: return s
1162
1163    @print_log_dec
1164    def bin(self, width=5, insitu=None):
1165        """
1166        Return a scan where all spectra have been binned up.
1167        Parameters:
1168            width:       The bin width (default=5) in pixels
1169            insitu:      if False a new scantable is returned.
1170                         Otherwise, the scaling is done in-situ
1171                         The default is taken from .asaprc (False)
1172        """
1173        if insitu is None: insitu = rcParams['insitu']
1174        self._math._setinsitu(insitu)
1175        varlist = vars()
1176        s = scantable(self._math._bin(self, width))
1177        s._add_history("bin", varlist)
1178        if insitu:
1179            self._assign(s)
1180        else:
1181            return s
1182
1183    @print_log_dec
1184    def resample(self, width=5, method='cubic', insitu=None):
1185        """
1186        Return a scan where all spectra have been binned up.
1187
1188        Parameters:
1189            width:       The bin width (default=5) in pixels
1190            method:      Interpolation method when correcting from a table.
1191                         Values are  "nearest", "linear", "cubic" (default)
1192                         and "spline"
1193            insitu:      if False a new scantable is returned.
1194                         Otherwise, the scaling is done in-situ
1195                         The default is taken from .asaprc (False)
1196        """
1197        if insitu is None: insitu = rcParams['insitu']
1198        self._math._setinsitu(insitu)
1199        varlist = vars()
1200        s = scantable(self._math._resample(self, method, width))
1201        s._add_history("resample", varlist)
1202        if insitu: self._assign(s)
1203        else: return s
1204
1205    @print_log_dec
1206    def average_pol(self, mask=None, weight='none'):
1207        """
1208        Average the Polarisations together.
1209        Parameters:
1210            mask:        An optional mask defining the region, where the
1211                         averaging will be applied. The output will have all
1212                         specified points masked.
1213            weight:      Weighting scheme. 'none' (default), 'var' (1/var(spec)
1214                         weighted), or 'tsys' (1/Tsys**2 weighted)
1215        """
1216        varlist = vars()
1217        mask = mask or ()
1218        s = scantable(self._math._averagepol(self, mask, weight.upper()))
1219        s._add_history("average_pol", varlist)
1220        return s
1221
1222    @print_log_dec
1223    def average_beam(self, mask=None, weight='none'):
1224        """
1225        Average the Beams together.
1226        Parameters:
1227            mask:        An optional mask defining the region, where the
1228                         averaging will be applied. The output will have all
1229                         specified points masked.
1230            weight:      Weighting scheme. 'none' (default), 'var' (1/var(spec)
1231                         weighted), or 'tsys' (1/Tsys**2 weighted)
1232        """
1233        varlist = vars()
1234        mask = mask or ()
1235        s = scantable(self._math._averagebeams(self, mask, weight.upper()))
1236        s._add_history("average_beam", varlist)
1237        return s
1238
1239    def parallactify(self, pflag):
1240        """
1241        Set a flag to inidcate whether this data should be treated as having
1242        been 'parallactified' (total phase == 0.0)
1243        Parameters:
1244            pflag:  Bool inidcating whether to turn this on (True) or
1245                    off (False)
1246        """
1247        varlist = vars()
1248        self._parallactify(pflag)
1249        self._add_history("parallactify", varlist)
1250
1251    @print_log_dec
1252    def convert_pol(self, poltype=None):
1253        """
1254        Convert the data to a different polarisation type.
1255        Note that you will need cross-polarisation terms for most conversions.
1256        Parameters:
1257            poltype:    The new polarisation type. Valid types are:
1258                        "linear", "circular", "stokes" and "linpol"
1259        """
1260        varlist = vars()
1261        try:
1262            s = scantable(self._math._convertpol(self, poltype))
1263        except RuntimeError, msg:
1264            if rcParams['verbose']:
1265                print msg
1266                return
1267            else:
1268                raise
1269        s._add_history("convert_pol", varlist)
1270        return s
1271
1272    @print_log_dec
1273    def smooth(self, kernel="hanning", width=5.0, order=2, insitu=None):
1274        """
1275        Smooth the spectrum by the specified kernel (conserving flux).
1276        Parameters:
1277            kernel:     The type of smoothing kernel. Select from
1278                        'hanning' (default), 'gaussian', 'boxcar', 'rmedian'
1279                        or 'poly'
1280            width:      The width of the kernel in pixels. For hanning this is
1281                        ignored otherwise it defauls to 5 pixels.
1282                        For 'gaussian' it is the Full Width Half
1283                        Maximum. For 'boxcar' it is the full width.
1284                        For 'rmedian' and 'poly' it is the half width.
1285            order:      Optional parameter for 'poly' kernel (default is 2), to
1286                        specify the order of the polnomial. Ignored by all other
1287                        kernels.
1288            insitu:     if False a new scantable is returned.
1289                        Otherwise, the scaling is done in-situ
1290                        The default is taken from .asaprc (False)
1291        Example:
1292             none
1293        """
1294        if insitu is None: insitu = rcParams['insitu']
1295        self._math._setinsitu(insitu)
1296        varlist = vars()
1297        s = scantable(self._math._smooth(self, kernel.lower(), width, order))
1298        s._add_history("smooth", varlist)
1299        if insitu: self._assign(s)
1300        else: return s
1301
1302    @print_log_dec
1303    def poly_baseline(self, mask=None, order=0, plot=False, uselin=False,
1304                      insitu=None):
1305        """
1306        Return a scan which has been baselined (all rows) by a polynomial.
1307        Parameters:
1308            mask:       an optional mask
1309            order:      the order of the polynomial (default is 0)
1310            plot:       plot the fit and the residual. In this each
1311                        indivual fit has to be approved, by typing 'y'
1312                        or 'n'
1313            uselin:     use linear polynomial fit
1314            insitu:     if False a new scantable is returned.
1315                        Otherwise, the scaling is done in-situ
1316                        The default is taken from .asaprc (False)
1317        Example:
1318            # return a scan baselined by a third order polynomial,
1319            # not using a mask
1320            bscan = scan.poly_baseline(order=3)
1321        """
1322        if insitu is None: insitu = rcParams['insitu']
1323        varlist = vars()
1324        if mask is None:
1325            mask = [True for i in xrange(self.nchan(-1))]
1326        from asap.asapfitter import fitter
1327        try:
1328            f = fitter()
1329            f.set_scan(self, mask)
1330            #f.set_function(poly=order)
1331            if uselin:
1332                f.set_function(lpoly=order)
1333            else:
1334                f.set_function(poly=order)
1335            s = f.auto_fit(insitu, plot=plot)
1336            s._add_history("poly_baseline", varlist)
1337            if insitu: self._assign(s)
1338            else: return s
1339        except RuntimeError:
1340            msg = "The fit failed, possibly because it didn't converge."
1341            if rcParams['verbose']:
1342                print msg
1343                return
1344            else:
1345                raise RuntimeError(msg)
1346
1347    def auto_poly_baseline(self, mask=[], edge=(0, 0), order=0,
1348                           threshold=3, chan_avg_limit=1, plot=False,
1349                           insitu=None):
1350        """
1351        Return a scan which has been baselined (all rows) by a polynomial.
1352        Spectral lines are detected first using linefinder and masked out
1353        to avoid them affecting the baseline solution.
1354
1355        Parameters:
1356            mask:       an optional mask retreived from scantable
1357            edge:       an optional number of channel to drop at
1358                        the edge of spectrum. If only one value is
1359                        specified, the same number will be dropped from
1360                        both sides of the spectrum. Default is to keep
1361                        all channels. Nested tuples represent individual
1362                        edge selection for different IFs (a number of spectral
1363                        channels can be different)
1364            order:      the order of the polynomial (default is 0)
1365            threshold:  the threshold used by line finder. It is better to
1366                        keep it large as only strong lines affect the
1367                        baseline solution.
1368            chan_avg_limit:
1369                        a maximum number of consequtive spectral channels to
1370                        average during the search of weak and broad lines.
1371                        The default is no averaging (and no search for weak
1372                        lines). If such lines can affect the fitted baseline
1373                        (e.g. a high order polynomial is fitted), increase this
1374                        parameter (usually values up to 8 are reasonable). Most
1375                        users of this method should find the default value
1376                        sufficient.
1377            plot:       plot the fit and the residual. In this each
1378                        indivual fit has to be approved, by typing 'y'
1379                        or 'n'
1380            insitu:     if False a new scantable is returned.
1381                        Otherwise, the scaling is done in-situ
1382                        The default is taken from .asaprc (False)
1383
1384        Example:
1385            scan2=scan.auto_poly_baseline(order=7)
1386        """
1387        if insitu is None: insitu = rcParams['insitu']
1388        varlist = vars()
1389        from asap.asapfitter import fitter
1390        from asap.asaplinefind import linefinder
1391        from asap import _is_sequence_or_number as _is_valid
1392
1393        # check whether edge is set up for each IF individually
1394        individualedge = False;
1395        if len(edge) > 1:
1396            if isinstance(edge[0], list) or isinstance(edge[0], tuple):
1397                individualedge = True;
1398
1399        if not _is_valid(edge, int) and not individualedge:
1400            raise ValueError, "Parameter 'edge' has to be an integer or a \
1401            pair of integers specified as a tuple. Nested tuples are allowed \
1402            to make individual selection for different IFs."
1403
1404        curedge = (0, 0)
1405        if individualedge:
1406            for edgepar in edge:
1407                if not _is_valid(edgepar, int):
1408                    raise ValueError, "Each element of the 'edge' tuple has \
1409                                       to be a pair of integers or an integer."
1410        else:
1411            curedge = edge;
1412
1413        # setup fitter
1414        f = fitter()
1415        f.set_function(poly=order)
1416
1417        # setup line finder
1418        fl = linefinder()
1419        fl.set_options(threshold=threshold,avg_limit=chan_avg_limit)
1420
1421        if not insitu:
1422            workscan = self.copy()
1423        else:
1424            workscan = self
1425
1426        fl.set_scan(workscan)
1427
1428        rows = range(workscan.nrow())
1429        asaplog.push("Processing:")
1430        for r in rows:
1431            msg = " Scan[%d] Beam[%d] IF[%d] Pol[%d] Cycle[%d]" % \
1432                (workscan.getscan(r), workscan.getbeam(r), workscan.getif(r), \
1433                 workscan.getpol(r), workscan.getcycle(r))
1434            asaplog.push(msg, False)
1435
1436            # figure out edge parameter
1437            if individualedge:
1438                if len(edge) >= workscan.getif(r):
1439                    raise RuntimeError, "Number of edge elements appear to " \
1440                                        "be less than the number of IFs"
1441                    curedge = edge[workscan.getif(r)]
1442
1443            # setup line finder
1444            fl.find_lines(r, mask, curedge)
1445            f.set_data(workscan._getabcissa(r),  workscan._getspectrum(r),
1446                        mask_and(workscan._getmask(r), fl.get_mask()))
1447            f.fit()
1448            x = f.get_parameters()
1449            if plot:
1450                f.plot(residual=True)
1451                x = raw_input("Accept fit ( [y]/n ): ")
1452                if x.upper() == 'N':
1453                    continue
1454            workscan._setspectrum(f.fitter.getresidual(), r)
1455        if plot:
1456            f._p.unmap()
1457            f._p = None
1458        workscan._add_history("auto_poly_baseline", varlist)
1459        if insitu:
1460            self._assign(workscan)
1461        else:
1462            return workscan
1463
1464    @print_log_dec
1465    def rotate_linpolphase(self, angle):
1466        """
1467        Rotate the phase of the complex polarization O=Q+iU correlation.
1468        This is always done in situ in the raw data.  So if you call this
1469        function more than once then each call rotates the phase further.
1470        Parameters:
1471            angle:   The angle (degrees) to rotate (add) by.
1472        Examples:
1473            scan.rotate_linpolphase(2.3)
1474        """
1475        varlist = vars()
1476        self._math._rotate_linpolphase(self, angle)
1477        self._add_history("rotate_linpolphase", varlist)
1478        return
1479
1480    @print_log_dec
1481    def rotate_xyphase(self, angle):
1482        """
1483        Rotate the phase of the XY correlation.  This is always done in situ
1484        in the data.  So if you call this function more than once
1485        then each call rotates the phase further.
1486        Parameters:
1487            angle:   The angle (degrees) to rotate (add) by.
1488        Examples:
1489            scan.rotate_xyphase(2.3)
1490        """
1491        varlist = vars()
1492        self._math._rotate_xyphase(self, angle)
1493        self._add_history("rotate_xyphase", varlist)
1494        return
1495
1496    @print_log_dec
1497    def swap_linears(self):
1498        """
1499        Swap the linear polarisations XX and YY, or better the first two
1500        polarisations as this also works for ciculars.
1501        """
1502        varlist = vars()
1503        self._math._swap_linears(self)
1504        self._add_history("swap_linears", varlist)
1505        return
1506
1507    @print_log_dec
1508    def invert_phase(self):
1509        """
1510        Invert the phase of the complex polarisation
1511        """
1512        varlist = vars()
1513        self._math._invert_phase(self)
1514        self._add_history("invert_phase", varlist)
1515        return
1516
1517    @print_log_dec
1518    def add(self, offset, insitu=None):
1519        """
1520        Return a scan where all spectra have the offset added
1521        Parameters:
1522            offset:      the offset
1523            insitu:      if False a new scantable is returned.
1524                         Otherwise, the scaling is done in-situ
1525                         The default is taken from .asaprc (False)
1526        """
1527        if insitu is None: insitu = rcParams['insitu']
1528        self._math._setinsitu(insitu)
1529        varlist = vars()
1530        s = scantable(self._math._unaryop(self, offset, "ADD", False))
1531        s._add_history("add", varlist)
1532        if insitu:
1533            self._assign(s)
1534        else:
1535            return s
1536
1537    @print_log_dec
1538    def scale(self, factor, tsys=True, insitu=None):
1539        """
1540        Return a scan where all spectra are scaled by the give 'factor'
1541        Parameters:
1542            factor:      the scaling factor
1543            insitu:      if False a new scantable is returned.
1544                         Otherwise, the scaling is done in-situ
1545                         The default is taken from .asaprc (False)
1546            tsys:        if True (default) then apply the operation to Tsys
1547                         as well as the data
1548        """
1549        if insitu is None: insitu = rcParams['insitu']
1550        self._math._setinsitu(insitu)
1551        varlist = vars()
1552        s = scantable(self._math._unaryop(self, factor, "MUL", tsys))
1553        s._add_history("scale", varlist)
1554        if insitu:
1555            self._assign(s)
1556        else:
1557            return s
1558
1559    def set_sourcetype(self, match, matchtype="pattern",
1560                       sourcetype="reference"):
1561        """
1562        Set the type of the source to be an source or reference scan
1563        using the provided pattern:
1564        Parameters:
1565            match:          a Unix style pattern, regular expression or selector
1566            matchtype:      'pattern' (default) UNIX style pattern or
1567                            'regex' regular expression
1568            sourcetype:     the type of the source to use (source/reference)
1569        """
1570        varlist = vars()
1571        basesel = self.get_selection()
1572        stype = -1
1573        if sourcetype.lower().startswith("r"):
1574            stype = 1
1575        elif sourcetype.lower().startswith("s"):
1576            stype = 0
1577        else:
1578            raise ValueError("Illegal sourcetype use s(ource) or r(eference)")
1579        if matchtype.lower().startswith("p"):
1580            matchtype = "pattern"
1581        elif matchtype.lower().startswith("r"):
1582            matchtype = "regex"
1583        else:
1584            raise ValueError("Illegal matchtype, use p(attern) or r(egex)")
1585        sel = selector()
1586        if isinstance(match, selector):
1587            sel = match
1588        else:
1589            sel.set_query("SRCNAME == %s('%s')" % (matchtype, match))
1590        self.set_selection(basesel+sel)
1591        self._setsourcetype(stype)
1592        self.set_selection(basesel)
1593        self._add_history("set_sourcetype", varlist)
1594
1595    @print_log_dec
1596    def auto_quotient(self, preserve=True, mode='paired'):
1597        """
1598        This function allows to build quotients automatically.
1599        It assumes the observation to have the same numer of
1600        "ons" and "offs"
1601        Parameters:
1602            preserve:       you can preserve (default) the continuum or
1603                            remove it.  The equations used are
1604                            preserve: Output = Toff * (on/off) - Toff
1605                            remove:   Output = Toff * (on/off) - Ton
1606            mode:           the on/off detection mode
1607                            'paired' (default)
1608                            identifies 'off' scans by the
1609                            trailing '_R' (Mopra/Parkes) or
1610                            '_e'/'_w' (Tid) and matches
1611                            on/off pairs from the observing pattern
1612                            'time'
1613                            finds the closest off in time
1614
1615        """
1616        modes = ["time", "paired"]
1617        if not mode in modes:
1618            msg = "please provide valid mode. Valid modes are %s" % (modes)
1619            raise ValueError(msg)
1620        varlist = vars()
1621        s = None
1622        if mode.lower() == "paired":
1623            basesel = self.get_selection()
1624            sel = selector()+basesel
1625            sel.set_query("SRCTYPE==1")
1626            self.set_selection(sel)
1627            offs = self.copy()
1628            sel.set_query("SRCTYPE==0")
1629            self.set_selection(sel)
1630            ons = self.copy()
1631            s = scantable(self._math._quotient(ons, offs, preserve))
1632            self.set_selection(basesel)
1633        elif mode.lower() == "time":
1634            s = scantable(self._math._auto_quotient(self, mode, preserve))
1635        s._add_history("auto_quotient", varlist)
1636        return s
1637
1638    @print_log_dec
1639    def mx_quotient(self, mask = None, weight='median', preserve=True):
1640        """
1641        Form a quotient using "off" beams when observing in "MX" mode.
1642        Parameters:
1643            mask:           an optional mask to be used when weight == 'stddev'
1644            weight:         How to average the off beams.  Default is 'median'.
1645            preserve:       you can preserve (default) the continuum or
1646                            remove it.  The equations used are
1647                            preserve: Output = Toff * (on/off) - Toff
1648                            remove:   Output = Toff * (on/off) - Ton
1649        """
1650        mask = mask or ()
1651        varlist = vars()
1652        on = scantable(self._math._mx_extract(self, 'on'))
1653        preoff = scantable(self._math._mx_extract(self, 'off'))
1654        off = preoff.average_time(mask=mask, weight=weight, scanav=False)
1655        from asapmath  import quotient
1656        q = quotient(on, off, preserve)
1657        q._add_history("mx_quotient", varlist)
1658        return q
1659
1660    @print_log_dec
1661    def freq_switch(self, insitu=None):
1662        """
1663        Apply frequency switching to the data.
1664        Parameters:
1665            insitu:      if False a new scantable is returned.
1666                         Otherwise, the swictching is done in-situ
1667                         The default is taken from .asaprc (False)
1668        Example:
1669            none
1670        """
1671        if insitu is None: insitu = rcParams['insitu']
1672        self._math._setinsitu(insitu)
1673        varlist = vars()
1674        s = scantable(self._math._freqswitch(self))
1675        s._add_history("freq_switch", varlist)
1676        if insitu: self._assign(s)
1677        else: return s
1678
1679    @print_log_dec
1680    def recalc_azel(self):
1681        """
1682        Recalculate the azimuth and elevation for each position.
1683        Parameters:
1684            none
1685        Example:
1686        """
1687        varlist = vars()
1688        self._recalcazel()
1689        self._add_history("recalc_azel", varlist)
1690        return
1691
1692    @print_log_dec
1693    def __add__(self, other):
1694        varlist = vars()
1695        s = None
1696        if isinstance(other, scantable):
1697            s = scantable(self._math._binaryop(self, other, "ADD"))
1698        elif isinstance(other, float):
1699            s = scantable(self._math._unaryop(self, other, "ADD", False))
1700        else:
1701            raise TypeError("Other input is not a scantable or float value")
1702        s._add_history("operator +", varlist)
1703        return s
1704
1705    @print_log_dec
1706    def __sub__(self, other):
1707        """
1708        implicit on all axes and on Tsys
1709        """
1710        varlist = vars()
1711        s = None
1712        if isinstance(other, scantable):
1713            s = scantable(self._math._binaryop(self, other, "SUB"))
1714        elif isinstance(other, float):
1715            s = scantable(self._math._unaryop(self, other, "SUB", False))
1716        else:
1717            raise TypeError("Other input is not a scantable or float value")
1718        s._add_history("operator -", varlist)
1719        return s
1720
1721    @print_log_dec
1722    def __mul__(self, other):
1723        """
1724        implicit on all axes and on Tsys
1725        """
1726        varlist = vars()
1727        s = None
1728        if isinstance(other, scantable):
1729            s = scantable(self._math._binaryop(self, other, "MUL"))
1730        elif isinstance(other, float):
1731            s = scantable(self._math._unaryop(self, other, "MUL", False))
1732        else:
1733            raise TypeError("Other input is not a scantable or float value")
1734        s._add_history("operator *", varlist)
1735        return s
1736
1737
1738    @print_log_dec
1739    def __div__(self, other):
1740        """
1741        implicit on all axes and on Tsys
1742        """
1743        varlist = vars()
1744        s = None
1745        if isinstance(other, scantable):
1746            s = scantable(self._math._binaryop(self, other, "DIV"))
1747        elif isinstance(other, float):
1748            if other == 0.0:
1749                raise ZeroDivisionError("Dividing by zero is not recommended")
1750            s = scantable(self._math._unaryop(self, other, "DIV", False))
1751        else:
1752            raise TypeError("Other input is not a scantable or float value")
1753        s._add_history("operator /", varlist)
1754        return s
1755
1756    def get_fit(self, row=0):
1757        """
1758        Print or return the stored fits for a row in the scantable
1759        Parameters:
1760            row:    the row which the fit has been applied to.
1761        """
1762        if row > self.nrow():
1763            return
1764        from asap.asapfit import asapfit
1765        fit = asapfit(self._getfit(row))
1766        if rcParams['verbose']:
1767            print fit
1768            return
1769        else:
1770            return fit.as_dict()
1771
1772    def flag_nans(self):
1773        """
1774        Utility function to flag NaN values in the scantable.
1775        """
1776        import numpy
1777        basesel = self.get_selection()
1778        for i in range(self.nrow()):
1779            sel = self.get_row_selector(i)
1780            self.set_selection(basesel+sel)
1781            nans = numpy.isnan(self._getspectrum(0))
1782        if numpy.any(nans):
1783            bnans = [ bool(v) for v in nans]
1784            self.flag(bnans)
1785        self.set_selection(basesel)
1786
1787    def get_row_selector(self, rowno):
1788        return selector(beams=self.getbeam(rowno),
1789                        ifs=self.getif(rowno),
1790                        pols=self.getpol(rowno),
1791                        scans=self.getscan(rowno),
1792                        cycles=self.getcycle(rowno))
1793
1794    def _add_history(self, funcname, parameters):
1795        if not rcParams['scantable.history']:
1796            return
1797        # create date
1798        sep = "##"
1799        from datetime import datetime
1800        dstr = datetime.now().strftime('%Y/%m/%d %H:%M:%S')
1801        hist = dstr+sep
1802        hist += funcname+sep#cdate+sep
1803        if parameters.has_key('self'): del parameters['self']
1804        for k, v in parameters.iteritems():
1805            if type(v) is dict:
1806                for k2, v2 in v.iteritems():
1807                    hist += k2
1808                    hist += "="
1809                    if isinstance(v2, scantable):
1810                        hist += 'scantable'
1811                    elif k2 == 'mask':
1812                        if isinstance(v2, list) or isinstance(v2, tuple):
1813                            hist += str(self._zip_mask(v2))
1814                        else:
1815                            hist += str(v2)
1816                    else:
1817                        hist += str(v2)
1818            else:
1819                hist += k
1820                hist += "="
1821                if isinstance(v, scantable):
1822                    hist += 'scantable'
1823                elif k == 'mask':
1824                    if isinstance(v, list) or isinstance(v, tuple):
1825                        hist += str(self._zip_mask(v))
1826                    else:
1827                        hist += str(v)
1828                else:
1829                    hist += str(v)
1830            hist += sep
1831        hist = hist[:-2] # remove trailing '##'
1832        self._addhistory(hist)
1833
1834
1835    def _zip_mask(self, mask):
1836        mask = list(mask)
1837        i = 0
1838        segments = []
1839        while mask[i:].count(1):
1840            i += mask[i:].index(1)
1841            if mask[i:].count(0):
1842                j = i + mask[i:].index(0)
1843            else:
1844                j = len(mask)
1845            segments.append([i, j])
1846            i = j
1847        return segments
1848
1849    def _get_ordinate_label(self):
1850        fu = "("+self.get_fluxunit()+")"
1851        import re
1852        lbl = "Intensity"
1853        if re.match(".K.", fu):
1854            lbl = "Brightness Temperature "+ fu
1855        elif re.match(".Jy.", fu):
1856            lbl = "Flux density "+ fu
1857        return lbl
1858
1859    def _check_ifs(self):
1860        nchans = [self.nchan(i) for i in range(self.nif(-1))]
1861        nchans = filter(lambda t: t > 0, nchans)
1862        return (sum(nchans)/len(nchans) == nchans[0])
1863
1864    def _fill(self, names, unit, average):
1865        import os
1866        from asap._asap import stfiller
1867        first = True
1868        fullnames = []
1869        for name in names:
1870            name = os.path.expandvars(name)
1871            name = os.path.expanduser(name)
1872            if not os.path.exists(name):
1873                msg = "File '%s' does not exists" % (name)
1874                if rcParams['verbose']:
1875                    asaplog.push(msg)
1876                    print asaplog.pop().strip()
1877                    return
1878                raise IOError(msg)
1879            fullnames.append(name)
1880        if average:
1881            asaplog.push('Auto averaging integrations')
1882        stype = int(rcParams['scantable.storage'].lower() == 'disk')
1883        for name in fullnames:
1884            tbl = Scantable(stype)
1885            r = stfiller(tbl)
1886            rx = rcParams['scantable.reference']
1887            r._setreferenceexpr(rx)
1888            msg = "Importing %s..." % (name)
1889            asaplog.push(msg, False)
1890            print_log()
1891            r._open(name, -1, -1)
1892            r._read()
1893            if average:
1894                tbl = self._math._average((tbl, ), (), 'NONE', 'SCAN')
1895            if not first:
1896                tbl = self._math._merge([self, tbl])
1897            Scantable.__init__(self, tbl)
1898            r._close()
1899            del r, tbl
1900            first = False
1901        if unit is not None:
1902            self.set_fluxunit(unit)
1903        self.set_freqframe(rcParams['scantable.freqframe'])
1904
1905    def __getitem__(self, key):
1906        if key < 0:
1907            key += self.nrow()
1908        if key >= self.nrow():
1909            raise IndexError("Row index out of range.")
1910        return self._getspectrum(key)
1911
1912    def __setitem__(self, key, value):
1913        if key < 0:
1914            key += self.nrow()
1915        if key >= self.nrow():
1916            raise IndexError("Row index out of range.")
1917        if not hasattr(value, "__len__") or \
1918                len(value) > self.nchan(self.getif(key)):
1919            raise ValueError("Spectrum length doesn't match.")
1920        return self._setspectrum(value, key)
1921
1922    def __len__(self):
1923        return self.nrow()
1924
1925    def __iter__(self):
1926        for i in range(len(self)):
1927            yield self[i]
Note: See TracBrowser for help on using the repository browser.