Index: tags/Release2.1.0b/COPYING
===================================================================
--- tags/Release2.1.0b/COPYING	(revision 1196)
+++ tags/Release2.1.0b/COPYING	(revision 1196)
@@ -0,0 +1,23 @@
+Copyright (C) 2004,2005,2006
+ATNF
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 2 of the License, or (at your option)
+any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+675 Massachusetts Ave, Cambridge, MA 02139, USA.
+
+Correspondence concerning this software should be addressed as follows:
+       Internet email: asap@atnf.csiro.au
+       Postal address: Australia Telescope National Facility,
+                       P.O. Box 76,
+                       Epping, NSW, 2121,
+                       AUSTRALIA
Index: tags/Release2.1.0b/INSTALL
===================================================================
--- tags/Release2.1.0b/INSTALL	(revision 1196)
+++ tags/Release2.1.0b/INSTALL	(revision 1196)
@@ -0,0 +1,47 @@
+Requirements
+============
+
+* scons
+
+Installation
+============
+
+scons has several targets. It is recommended to execute them sequentially.
+The normal install procedure is as follows:
+   . <path_to_casa_asap>/aipsinit.sh
+   scons
+   # Do this if you haven't got a data directory already
+   export ASAPDATA=.
+   ./bin/asap_update_data
+   #
+   scons install
+
+
+Obtaining the data directory
+============================
+The data repository can be obtained seperately after installing ASAP.
+Before you run asap for the first time simply run
+    asap_update_data
+This will install the data repository into the correct location.
+
+There are several ways to do this. 
+a) If you already hae the 'data' directory, copy it to the root directory and run the normal install procedure.
+   scons
+   scons install
+
+b) You can obtain the data repository from the ftp site.
+   Simply run 
+   	export ASAPDATA=.
+     	./bin/asap_update_data
+   This will retrieve and unpack the data directory.
+   After this run: 
+   	scons install
+
+Creating a binary distribution
+==============================
+
+after building using 'scons', you can create a compressed tarball of the binaries. This is done via the target 'makedist='. The argument should be an obvious identifier for the system you want to support, e.g. 'sarge' or 'fc5-x86_64'
+
+    scons makedist=sarge
+
+This will create an archive in the subdirectory 'dist' named 'asap-X.Y.Z-sarge.tar.bz2', where X,Y,Z are the version numbers e.g 2.1.0.
Index: tags/Release2.1.0b/Makefile
===================================================================
--- tags/Release2.1.0b/Makefile	(revision 1196)
+++ tags/Release2.1.0b/Makefile	(revision 1196)
@@ -0,0 +1,62 @@
+PREFIX := /usr
+PYDIR := $(PREFIX)/lib/python2.3/site-packages
+
+ifndef ASAPROOT
+   ASAPROOT := $(shell pwd)
+endif
+
+PY := $(wildcard python/*.py)
+LIBS := /tmp/_asap.so
+BINS := bin/asap
+
+CASAROOT  := $(word 1, $(AIPSPATH))
+PLATFORM  := $(word 2, $(AIPSPATH))
+DISTDIR   := asap_$(PLATFORM)
+
+all: module #doc
+
+module:
+	@cd $(ASAPROOT)/src; make
+
+doc:
+	@cd $(ASAPROOT)/doc; make
+
+install:
+	@if ( test ! -d $(PYDIR)/asap ) ; then mkdir -p $(PYDIR)/asap ; fi
+	@if ( test ! -d $(PREFIX)/bin ) ; then mkdir -p $(PREFIX)/bin ; fi
+	@for file in $(LIBS) ; do cp -f $$file $(PYDIR)/asap ; done
+	@for file in $(BINS) ; do cp -f $$file $(PREFIX)/bin ; done
+	@for file in $(PY) ; do cp -f $$file $(PYDIR)/asap ; done
+	@if ( test ! -d $(PREFIX)/share/asap ) ; then mkdir -p $(PREFIX)/share/asap ; fi
+	@cp -f share/ipythonrc-asap $(PREFIX)/share/asap/
+	@echo "Successfully installed asap module to" $(PYDIR)
+
+clean:
+	@cd $(ASAPROOT)/src; make clean
+	@cd $(ASAPROOT)/doc; make clean
+
+datadist:
+	@echo "Generating ASAP data archive from aips++ installation..."
+	@cd $(CASAROOT); tar cfj $(ASAPROOT)/$(DISTDIR)/share/data.tar.bz2 data/ephemerides data/geodetic
+	@echo "...done."
+
+
+dist: module doc
+	@cd $(ASAPROOT)
+	@if ( test -d $(DISTDIR)  ) ; then rm -rf $(DISTDIR) ; fi
+	@mkdir $(DISTDIR)
+	@mkdir $(DISTDIR)/build $(DISTDIR)/bin $(DISTDIR)/share
+	@for file in $(LIBS) ; do cp -f $$file $(DISTDIR)/build/ ; done
+	@for file in $(PY) ; do cp -f $$file $(DISTDIR)/build/ ; done
+	@for file in $(BINS) ; do cp -f $$file $(DISTDIR)/bin/ ; done
+	@cp -f share/ipythonrc-asap $(DISTDIR)/share/
+	make datadist
+	@cp -f doc/README $(DISTDIR)/
+	@cp -f doc/CHANGELOG $(DISTDIR)/
+	@cp -f bin/install.sh $(DISTDIR)/bin/
+	@echo "Creating compressed archive..."
+	@tar jcf $(DISTDIR).tar.bz2 $(DISTDIR)
+	@rm -rf $(DISTDIR)/
+	@echo "Successfully created binary package" $(DISTDIR).tar.bz2
+
+.PHONY: install clean
Index: tags/Release2.1.0b/SConstruct
===================================================================
--- tags/Release2.1.0b/SConstruct	(revision 1196)
+++ tags/Release2.1.0b/SConstruct	(revision 1196)
@@ -0,0 +1,152 @@
+import os
+import sys
+import distutils.sysconfig
+import platform
+import SCons
+# scons plug-ins
+#from installtree import InstallTree
+
+moduledir = distutils.sysconfig.get_python_lib()
+if  platform.architecture()[0] == '64bit':
+    # hack to install into /usr/lib64 if scons is in the 32bit /usr/lib/
+    if moduledir.startswith("/usr/lib/"):
+        moduledir = moduledir.replace("lib", "lib64")
+
+opts = Options("userconfig.py")
+opts.AddOptions(PathOption("prefix",
+                           "The root installation path",
+                           distutils.sysconfig.PREFIX),
+                PathOption("moduledir",
+                            "The python module path (site-packages))",
+                            moduledir),
+                ("rpfitsdir", "Alternative rpfits location.", ""),
+                ("cfitsioincdir", "Alternative cfitsio include dir", ""),
+                ("casadir", "Alternative casa location", ""),
+                EnumOption("mode", "The type of build.", "debug",
+                           ["release","debug"], ignorecase=1),
+                ("makedist",
+                 "Make a binary archive giving a suffix, e.g. sarge or fc5",
+                 "")
+                )
+
+env = Environment( toolpath = ['./scons'],
+                   tools = ["default", "casa", "archiver", "utils",
+                            "quietinstall"],
+                   ENV = { 'PATH' : os.environ[ 'PATH' ],
+                          'HOME' : os.environ[ 'HOME' ] },
+                   options = opts)
+
+Help(opts.GenerateHelpText(env))
+env.SConsignFile()
+env.Append(CASAARCH = '', CASAROOT = '')
+if (os.path.exists(env["cfitsioincdir"])):
+    env.Append(CPPPATH=[env["cfitsioincdir"]])
+env.AddCustomPath(env["rpfitsdir"])
+if not env.GetOption('clean'):
+    conf = Configure(env)
+    # import Custom tests
+    env.AddCasaTest(conf)
+    pylib = 'python'+distutils.sysconfig.get_python_version()
+    pyinc = "Python.h"
+    if env['PLATFORM'] == "darwin":
+        pylib = "Python"
+    if not conf.CheckLib(library=pylib, language='c'): Exit(1)
+    conf.env.Append(CPPPATH=[distutils.sysconfig.get_python_inc()])
+    if not conf.CheckHeader("Python.h", language='c'):
+        Exit(1)
+    if not conf.CheckLibWithHeader('boost_python', 'boost/python.hpp', 'c++'): Exit(1)
+    conf.env.AddCustomPath(env["rpfitsdir"])
+    if not conf.CheckLib('rpfits'): Exit(1)
+    # cfitsio is either in include/ or /include/cfitsio
+    # handle this
+    if not conf.CheckLib(library='cfitsio', language='c'): Exit(1)
+    if not conf.CheckHeader('fitsio.h', language='c'):
+        if not conf.CheckHeader('cfitsio/fitsio.h', language='c'):
+            Exit(1)
+        else:
+            conf.env.Append(CPPPATH=['/usr/include/cfitsio'])
+    if (sys.platform == "darwin"):
+        conf.env.Append(LIBS = ['-framework vecLib'])
+    else:
+        if not conf.CheckLib('lapack'): Exit(1)
+        if not conf.CheckLib('blas'): Exit(1)
+    if not conf.CheckLib('g2c'): Exit(1)
+    if not conf.CheckLib('stdc++', language='c++'): Exit(1)
+    if not conf.CheckCasa(env["casadir"]): Exit(1)
+    env = conf.Finish()
+
+env["version"] = "2.1.0b"
+
+# general CPPFLAGS
+env.Append(CPPFLAGS=['-D_FILE_OFFSET_BITS=64', '-D_LARGEFILE_SOURCE', '-O3'])
+
+# 64bit flags
+if  platform.architecture()[0] == '64bit':
+    env.Append(CPPFLAGS=['-fPIC', '-D__x86_64__', '-DAIPS_64B'])
+
+if env['mode'] == 'release':
+    env.Append(LINKFLAGS=['-Wl,-O1'])
+
+# Export for SConscript files
+Export("env")
+
+# build library
+so = env.SConscript("src/SConscript", build_dir="build", duplicate=0)
+# test module import, to see if there are unresolved symbols
+def test_module(target,source,env):
+    pth = str(target[0])
+    mod = os.path.splitext(pth)[0]
+    sys.path.insert(2, os.path.split(mod)[0])
+    __import__(os.path.split(mod)[1])
+    print "ok"
+    return 0
+def test_str(target, source, env):
+    return "Testing module..."
+
+taction = Action(test_module, test_str)
+env.AddPostAction(so, taction)
+
+# install targets
+somod = env.Install("$moduledir/asap", so )
+pymods = env.Install("$moduledir/asap", env.SGlob("python/*.py"))
+bins = env.Install("$prefix/bin", ["bin/asap", "bin/asap_update_data"])
+shares = env.Install("$moduledir/asap/data", "share/ipythonrc-asap")
+env.Alias('install', [somod, pymods, bins, shares])
+
+# install aips++ data repos
+rootdir=None
+outdir =  os.path.join(env["moduledir"],'asap','data')
+sources = ['ephemerides','geodetic']
+if os.path.exists("/nfs/aips++/data"):
+    rootdir = "/nfs/aips++/data"
+elif os.path.exists("data"):
+    rootdir = "./data"
+if rootdir is not None:
+    ofiles, ifiles = env.WalkDirTree(outdir, rootdir, sources)
+    data =  env.InstallAs(ofiles, ifiles)
+    env.Alias('install', data)
+    
+# make binary distribution
+if len(env["makedist"]):
+    env["stagedir"] = "asap-%s-%s" % (env["version"], env["makedist"])
+    env.Command('Staging distribution for archive in %s' % env["stagedir"],
+                '', env.MessageAction)
+    st0 = env.QInstall("$stagedir/asap", [so,  env.SGlob("python/*.py")] )
+    env.QInstall("$stagedir/bin", ["bin/asap", "bin/asap_update_data"])
+    env.QInstall("$stagedir", ["bin/install"])
+    env.QInstall("$stagedir/asap/data", "share/ipythonrc-asap")
+    if rootdir is not None:
+        env.Command("Using data tables in %s" % rootdir,
+                    '', env.MessageAction)        
+        outdir =  os.path.join(env["stagedir"],'asap','data')
+        ofiles, ifiles = env.WalkDirTree(outdir, rootdir, sources)
+        env.QInstallAs(ofiles, ifiles)
+    else:
+        env.Command("No data tables available. Use 'asap_update_data' after install",
+                    '', env.MessageAction)        
+    arch = env.Archiver(os.path.join("dist",env["stagedir"]),
+                        env["stagedir"])
+    env.AddPostAction(arch, Delete("$stagedir"))
+
+if env.GetOption("clean"):
+    Execute(Delete(".sconf_temp"))
Index: tags/Release2.1.0b/admin/generate_data.sh
===================================================================
--- tags/Release2.1.0b/admin/generate_data.sh	(revision 1196)
+++ tags/Release2.1.0b/admin/generate_data.sh	(revision 1196)
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+if [ "$AIPSPATH" != "" ]; then
+    a_root=`/bin/echo $AIPSPATH | awk '{print $1}'`
+else
+    echo "No aips++ installation found"
+    exit 1
+fi
+    
+target="`pwd`/data.tar.gz"
+cd $a_root
+echo "Generating ASAP data archive ${target} from aips++ installation."
+tar cfz ${target} data/ephemerides data/geodetic
+echo "Done."
Index: tags/Release2.1.0b/admin/log2req.pl
===================================================================
--- tags/Release2.1.0b/admin/log2req.pl	(revision 1196)
+++ tags/Release2.1.0b/admin/log2req.pl	(revision 1196)
@@ -0,0 +1,31 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+use Getopt::Long;
+
+my $purge = 0;
+
+GetOptions('purge'=>\$purge);
+
+my @log = <>;
+my $log = join '', @log;
+
+while ($log =~ /REQUIREMENT:\s*(R.*?):.?E.?N.?D.?R.?E.?Q/sg) {
+  $_ = $1;
+  s/\n//;
+
+  if ($purge) {
+    next if (/& Deferred &/);
+    next if (/& Obsolete &/);
+    next if (/& Done.? &/);
+    next if (/& Removed &/);
+  }
+
+  s/& Done1 & /& Completed &/;
+  s/& Done & /& Completed &/;
+
+  s/(&[^&]*){2}$//;
+
+  print "$_ \\\\\n";
+}
Index: tags/Release2.1.0b/admin/newreq.sh
===================================================================
--- tags/Release2.1.0b/admin/newreq.sh	(revision 1196)
+++ tags/Release2.1.0b/admin/newreq.sh	(revision 1196)
@@ -0,0 +1,7 @@
+#!/bin/sh -f
+
+touch reqsum.tex
+latex requirements2
+\rm reqsum.tex
+./log2req.pl -purge requirements2.log > reqsum.tex
+latex requirements2
Index: tags/Release2.1.0b/admin/reqtable.tex
===================================================================
--- tags/Release2.1.0b/admin/reqtable.tex	(revision 1196)
+++ tags/Release2.1.0b/admin/reqtable.tex	(revision 1196)
@@ -0,0 +1,29 @@
+\documentclass[11pt]{article}
+\usepackage{a4}
+\usepackage{longtable}
+
+% Adjust the page size
+\addtolength{\oddsidemargin}{-0.4in}
+\addtolength{\evensidemargin}{+0.4in}
+\addtolength{\textwidth}{+0.8in}
+
+\setlength{\parindent}{0mm}
+\setlength{\parskip}{1ex}
+
+
+\begin{document}
+
+\begin{longtable}{|l|l|l|c|l|}
+
+\hline
+\bf Requirement & & \bf Status & \bf Priority & \bf Effort \\
+\hline
+\endhead
+\hline
+\endfoot
+\input{reqsum.tex}
+
+\end{longtable}
+
+
+\end{document}
Index: tags/Release2.1.0b/admin/requirements1.tex
===================================================================
--- tags/Release2.1.0b/admin/requirements1.tex	(revision 1196)
+++ tags/Release2.1.0b/admin/requirements1.tex	(revision 1196)
@@ -0,0 +1,1060 @@
+\documentclass[11pt]{article}
+\usepackage{a4}
+\usepackage{calc}
+\usepackage{smartref}
+\usepackage[dvips]{graphicx}
+
+% Adjust the page size
+\addtolength{\oddsidemargin}{-0.4in}
+\addtolength{\evensidemargin}{+0.4in}
+\addtolength{\textwidth}{+0.8in}
+
+\setlength{\parindent}{0mm}
+\setlength{\parskip}{1ex}
+
+
+\title{ATNF Single Dish Spectral Line Software Requirements }
+\author{Chris Phillips \& Malte Marquarding}
+
+\newcounter{requirement}
+\addtoreflist{requirement}
+\newcommand{\reqref}[1]{R\ref{#1}-\requirementref{#1}}
+
+\newcommand{\makenote}[1]{{\bf \tt \em#1}}
+
+\newcommand{\anitem}[2]{\smallskip \parbox[t]{2cm}{#1}\parbox[t]{\textwidth-2cm}{#2}}
+
+\newcommand{\showreqcounter}{{\stepcounter{requirement}
+    \bf R\arabic{section}.\arabic{subsection}-\arabic{requirement}}}
+
+\newcommand{\requirement}[2]{
+  \hspace*{2mm}\begin{minipage}{\textwidth-2mm}
+    \setlength{\parindent}{-2mm}
+    \showreqcounter\ #1 \\ 
+    \hspace*{1cm} {\em Priority #2}
+  \end{minipage}
+}
+
+\newcommand{\extendedrequirement}[2]{
+  \hspace*{2mm}\begin{minipage}{\textwidth-2mm}
+    \setlength{\parindent}{-2mm}
+    \showreqcounter\ #1 
+    \hspace*{1cm} {\em Priority #2}
+  \end{minipage}
+}
+
+\newcommand{\reqeqn}[1]{\\\hspace*{1cm} $#1$}
+
+\let\oldsection\section
+\renewcommand{\section}[1]{\setcounter{requirement}{0}\oldsection{#1}}
+
+\let\oldsubsection\subsection
+\renewcommand{\subsection}[1]{\setcounter{requirement}{0}\oldsubsection{#1}}
+
+\begin{document}
+
+\maketitle
+
+\section{Introduction}
+
+The spectral line single-dish software {\tt spc} at the ATNF has
+become increasingly difficult to maintain. There also have been many
+requests for features which are not possible to implement without
+major change to this package. The interface is based on outdated
+operating systems and programming languages. The decision was made to
+replace {\tt spc} with a new package which supports existing and
+future ATNF single-dish instrumentation. A survey of users was taken
+into account creating this requirements document.
+
+\section{Scope}
+
+The software should be able to process all spectral line single-dish
+observations from ATNF telescopes (Parkes, Mopra \& Tidbinbilla). This
+includes reading the data produced by the telescope, calibration and
+reduction of the data and basic analysis of the data such as fitting
+line profiles.
+
+It has been assumed that the following processing is out of the scope
+of the new software.
+\begin{itemize}
+\item Raster or ``on-the-fly'' mapping (This is handled by
+``livedata'' and gridzilla).
+\item Very complex or specific data processing. (A route into
+  Class\footnote{Part of the GLIDAS software package, produced by
+  Institut de Radio Astronomie Millime\'trique http://www.iram.fr}
+  should be available for advanced processing).
+%%TODO%% give example
+\item Continuum data.
+\item Pulsar timing observations.
+\end{itemize}
+
+\section{Priorities}
+
+Requirements have been given a value of 0 to 3. The ``0'' priority
+functions will be implemented in a demonstrator version of the
+software (with no GUI interface). The other requirements will be
+implemented mainly depending on priority, with ``1'' the
+highest. Priority 3 and some priority 2 requirements will probably not
+get implemented in the first released version of the software.
+
+\section{User Interface}
+
+The user interface (UI) is the most important part of a single dish
+processing package, but probably the most difficult to get right. The
+UI for this software will consist of three parts.
+\begin{itemize}
+  \item A graphical user interface (GUI).
+  \item An interactive command line interface (CLI).
+  \item A scriptable interface for batch processing.
+\end{itemize}
+
+The CLI and scriptable interface may essentially be the same.
+
+The software does not {\em need} to be able to run solely from a
+``vt100'' style terminal. It can be assumed that the user is running
+the software from within a windowed (i.e. X11) environment. This will
+mean it will not necessarily be possible to run the software remotely
+over a slow network connection (e.g. internationally or from home).
+Where possible, operations on the data should be possible from all
+three aspects of the user interface. 
+
+The user interface needs to be implemented so that the user can easily
+and transparently work on spectra either one at a time or by
+processing multiple spectra in parallel. This means there must be an
+easy way to select specific or multiple spectra to display or process.
+
+\subsection{Graphical User Interface}
+
+The GUI is intended to be the main interactive interface with the
+software. 
+
+\smallskip
+\requirement{It should be simple, intuitive and
+uncluttered. Specifically, use of many windows simultaneously should
+be discouraged, as should hiding functionality behind layers of dialog
+boxes.}{1}
+
+\requirement{The plotting window should be a major component of the GUI
+control, not a separate isolated window.}{1}
+
+\requirement{The interface should use minimal ``always visible''
+  controls, with use of pull down menus and maybe a toolbar for
+  frequency used functions. }{1}
+
+\requirement{Keyboard shortcuts should be available.}{2}
+
+\requirement{Most user preferences (i.e. keywords in the CLI) should
+be presented in a popup, tabbed, dialog box.}{2}
+
+\requirement{When performing line profile fitting, a spreadsheet type
+window should be viewable which shows the current parameter values
+(amplitude, velocity etc) for each line fitted and allow the user to
+change these parameters or set the current value as fixed. This gui
+should stay synchronised with any CLI changes to these values.}{2}
+
+\subsection{Command Line Interface}
+
+\requirement{While the GUI should be the main interface for new users
+and for basic manipulation, some tasks can be more efficiently
+performed using a CLI. A virtual CLI could be integrated as part of
+the GUI.}{1}
+
+\requirement{The CLI should have a keyword/argument form and never
+prompt the user for specific values (the user should be able to change
+values which are retained until they wants to change them again).}{1}
+
+\requirement{The CLI should be case insensitive and accept minimum
+matching and short forms of keywords.}{2}
+
+\requirement{The user must be able to quickly and easily see from the
+command line the available routines and keywords which affect it, so
+they can see which parameters may need changing.}{1}
+
+\subsection{Scripting}
+
+\requirement{It must be possible to run the software in a scripting
+mode. This would be to process large amounts of data in a routine
+manner and also to automatically reproduce specific plots etc (So the
+scripting must have full control of the plotter). Preferably the
+scripting ``language'' and the CLI would be the same. }{1}
+
+\requirement{It would be worthwhile having a method to auto-generate
+scripts (for reduction or plotting) from current spectra history, or
+some similar method.}{3}
+
+\section{Plotter}
+
+The plotter should be fully interactive and be an integral part of the
+GUI and software interface.
+
+\requirement{It must be able to produce plots of publishable
+quality. This includes being able to specify line thickness, character
+size, colours, position of axis ticks, axis titles etc and producing
+hard copies in postscript and .png format.}{0}
+
+\requirement{It must be possible to flexibly select the data to plot
+  (e.g. Tsys vs time etc as well as plots such as amplitude vs channel
+  number or velocity). Preferably any of the header values for a
+  selection of scans could be plotted on a scatter plot (e.g. Tsys vs
+  elevation)}{2}
+
+\requirement{It must be possible to overlay multiple spectra on a
+  single plot using different colours and/or different line
+  styles. (Including multiple stokes data and multiple IFs).}{1}
+
+\requirement{It must be possible to plot either the individual
+  integrations (in either a stacked fashion, or using a new subplot
+  per integration) or some type of average of all the integrations in
+  a scan.}{2}
+
+\requirement{Multi-panelling of spectra in an n$\times$m size grid. If
+  more spectra than can fit on the plot matrix are to be plotted, then
+  it must be possible to step back and forth between the viewable
+  spectra (i.e. ``multi-page'' plots). It must be possible to quickly
+  and easily change the number of plots per page, and define the ``n''
+  and ``m'' values.}{1}
+
+\requirement{When using multi-panelling, it should be possible to
+  easily change the display to flip between a single spectra display
+  and multi-panels display (i.e. zoom into a specific sub-window).}{3}
+
+\requirement{It must be possible to interactively zoom the plot
+  (channel range selection and amplitude of the spectra etc.)  This
+  includes both GUI control of the zooming as well as command line
+  control of either the zoom factor or directly specifying the zoom
+  bounds. }{1}
+
+\requirement{On a single plot, it should be possible to plot the full
+  spectrum and a zoomed copy of the data (using a different lie style)
+  to see weak features. The user must be able to specify the zoom
+  factor.}{3}
+
+\requirement{Optionally when stacking multiple spectral plots in one
+  subwindow, a (user definable) offset in the ``y'' direction should
+  be added to each subsequent spectra.}{2}
+
+\requirement{The plotter should automatically update to reflect user
+processing, either from the CLI or GUI. The user should have to option
+to turn this feature off if they so wish.}{2}
+
+\requirement{It should be possible to plot individual integrations
+(possibly from multiple scans) in a ``waterfall'' plot. This is an
+image based display, where spectral channel is along the x-axis of the
+plot, time (or integration number) along the y-axis and greyscale or
+colour represent the amplitude of spectra. Interactive zooming and
+panning of this image should be supported. }{3}
+
+\requirement{When plotting ``waterfall'' plots, it should be possible
+to interactively select regions or points and mark them as invalid
+(i.e. to remove RFI affected data). The plotter should also show the
+time/velocity of the pixel beneath the cursor.}{3}
+
+\requirement{It should be possible to export the ``waterfall'' plot
+images as a FITs file, for user specific analysis.}{3}
+
+\requirement{Line markers overlays, read from a catalogue should be
+  optionally available. This would include the full Lovas catalogue,
+  the JPL line catalogue, radio recombination lines and a simple user
+  definable catalogue. The lines would be Doppler corrected to the
+  line velocity. The user must be able to plot just a sub-section of
+  the lines in any specific catalogue (to avoid clutter).}{2}
+
+\requirement{Optionally plot fitted functions (e.g line profiles or
+  baseline fit). If multiple components (e.g. Gaussians) have been
+  fit, it should be possible to show the individual functions or the
+  sum of the components}{1}
+
+\requirement{It should be possible to plot the residual data with or
+  without subtraction of fit functions. This includes plotting the
+  spectra with or without baseline removal and the residual after
+  subtracting Gaussian fits. The default should be to plot the data
+  with baseline subtracted but profile fits not subtracted.}{2}
+
+\requirement{Basic header data (source name, molecule, observation
+  time etc) should be optionally shown, either on the plot or next to
+  it. This may either consist of a set of values, or only one or two
+  values the user specifically wants to see (source name and molecule,
+  for example). Preferably the user would be able to define where on
+  the plot the header info would appear.}{2}
+
+\requirement{Optionally, relevant data such as the current mouse
+  position should be displayed (maybe with a mode to display an
+  extended cross, horizontal or vertical line at the current cursor
+  position).}{2}
+
+\requirement{The user should be able to define simple
+  annotations. This would include text overlay and probably simple
+  graphics (lines, arrows etc).}{3}
+
+The user must be able to use the plotter window to interactively set
+initial values and ranges used for fitting functions etc. The use of
+keyboard ``shortcuts'' or other similar ``power user'' features should
+be available to save the time of experienced users. 
+
+The plotter should be used to set the following values:
+
+\requirement{Range of spectral points needed for specific tasks (See
+ requirement \reqref{ref:chansel})}{1}
+
+\requirement{Initial Gaussian parameters (velocity, width, amplitude)
+for profile fitting.}{1}
+
+\requirement{Change the parameter values of existing line profile
+fits, or channel ranges used for baseline fits.}{3}
+
+\section{Functionality}
+
+\subsection{Import/export}
+
+The software needs a set of import/export functions to deal with a
+variety of data formats and to be able to exchange data with other
+popular packages. These functions should be flexible enough to allow
+the user to perform analysis functions in an different package and
+re-import the data (or vice versa). The import function must be
+modular enough to easily add new file formats when the need arises.
+To properly import data, extra information may have to be read from
+secondary calibration files (such as GTP, Gated Total Power, for 3~mm
+wavelength data taken with Mopra). The import functions should be
+flexible enough to gracefully handle data files with missing headers
+etc. They should also be able to make telescope and date specific
+corrections to the data (for ATNF observatories).
+
+The software must be able to read (import) the following file formats.
+
+\requirement{The rpfits file format produced by all current ATNF
+  correlators.}{0}
+
+\requirement{SDFITS (currently written by {\tt SPC}).}{1}
+
+\requirement{Simple ``image'' FITS (used by CLASS}{2}
+
+\requirement{Historic ATNF single dish formats (Spectra, SPC,
+SLAP). Possibly a set of routines to translate these formats to SDFITs
+would suffice.}{3}
+
+\requirement{PSRFIT for pulsar spectroscopy.}
+
+\requirement{For online analysis, the software should be able to read
+an rpfits file which is is still currently open for writing by the
+telescope backend processor.}{1}
+
+\requirement{Data which has been observed in either a fixed frequency
+or Doppler tracked fashion needs to be handled transparently.}{1}
+
+The software should be able to export the data in the following formats.
+
+\requirement{Single Dish FITS.}{1}
+
+\requirement{Simple ``image'' FITS (used by CLASS. It must be possible
+to to export multiple spectra simultaneously, using default file name
+and postfix.}{2}
+
+\requirement{In a format which can be imported by other popular
+    packages such as Class. }{2}
+
+\requirement{Simple ascii format, suitable for use with programs such
+    as Perl, Python, SuperMongo etc.}{2}
+
+\requirement{The exported data should retain as much header data as
+possible. It should also be possible to request specific data be
+written in the desired form (B1950 coordinates, optical velocity
+definition etc).}{2}
+
+\requirement{The import function should apply relevant corrections
+(especially those which are time dependent) to specific
+telescopes. See $\S$\ref{sec:issues} for a list of currently known
+issues.}{1}
+
+\subsection{Sky subtraction}
+\label{sec:skysubtraction}
+To remove the effects of the passband filter shape and atmospheric
+fluctuations across the band, sky subtraction must be performed on the
+data. The software must be able to do sky subtraction using both
+position switching (quotient spectra) and frequency switching
+techniques.
+
+\requirement{\label{ref:skysub} Position switched sky subtraction
+should be implemented using the algorithm \medskip\reqeqn{T_{ref}
+\times \frac{S}{R} - T_{sig}} -- removes continuum\bigskip
+\reqeqn{T_{ref} \times \frac{S}{R} - T_{ref}} -- preserves
+continuum\medskip}{0}
+
+\requirement{The user should be able to specify an arbitrarily complex
+reference/source order (which repeats), which can then be used to make
+perform multiple sky subtractions in parallel.}{3}
+
+\requirement{Frequency switched sky subtraction should be
+	supported. (Ref. Liszt, 1997, A\&AS, 124, 183) }{2}
+
+%\requirement{For wideband multibit sampled data it may be desirable or
+%even required to assume Tsys has a frequency dependency. Appropriate
+%sky subtraction algorithms will need to be investigated.}{3}
+
+\requirement{For pulsar binned data, the (user specified) off pulse
+bins can be used as the reference spectra. Due to potentially rapid
+amplitude fluctuations, sky subtractions may need to be done on a per
+integration basis.}{3}
+
+Multibeam systems can observe in a nodding fashion (called MX mode at
+Parkes), where the telescope position is nodded between scans so that
+the source is observed in turn by two beams and a reference spectra
+for one beam is obtained while the other is observing the target source.
+
+\requirement{For multibeam systems, it must be possible to perform sky
+subtraction with the source and reference in an alternate pair of
+beams}{2}
+
+\subsection{Baseline removal}
+
+Baseline removal is needed to correct for imperfections in sky
+subtraction. Depending on the stability of the system, the residual
+spectral baseline errors can be small or quite large. Baseline removal
+is usually done by fitting a function to the (user specified) line
+free channels.
+
+\requirement{The software must be able to do baseline removal by
+  fitting a n'th order polynomials to the line free channels using a
+  least squares method.}{0}
+
+%\requirement{The baseline fitting should be reversible, i.e. the fit
+%parameters must be retained.}{2}
+
+\requirement{Removal of standing wave ripples should be done by
+fitting a Sine function to the line free channels.}{2}
+
+\requirement{``Robust'' fitting functions should be available, which are more
+  tolerant to RFI.}{1}
+
+\requirement{Automatic techniques for baselining should be
+  investigated.}{3}
+
+\subsection{Line Profile Fitting}
+
+The user will want to fit multicomponent line profiles to the data in
+a simple manner and be able to manipulate the exact fitting
+parameters.
+
+\requirement{The software must be able to do multi-component Gaussian
+  fitting of the spectra. The initial amplitude, width and velocity of
+  each component should be able to be set by the user and specific
+  values to be fit should be easily set.}{0}
+
+\requirement{The reduce Chi square (or similar statistic) of the fit
+should given to the user, so that they can easily see if adding extra
+components give a statistically significant improvement to the fit.}{1}
+
+%\requirement{The fit parameters should be stored with the data so that
+%the user can work on multiple data sets simultaneously and experiment
+%with different fitting values. These values should be saved to disk
+%along with the data.}{1}
+
+\requirement{For multiple polarisation data, the individual stokes
+parameters or polarisation products should be fit independently.}{1}
+
+\requirement{There should be an easy way of exporting the fit
+parameter from multiple spectra, e.g. as an ASCII table.}{2}
+
+\requirement{It should be also possible to do constrained fitting of
+multiple hyperfine components, e.g. the NH$_3$ hyperfine
+components. (The constraints may be either the frequency separation of
+the individual components or the amplitude ratio etc.)}{3}
+
+\requirement{It must be possible to alter the line profile fit
+parameter values by hand at any stage.}{2}
+
+\requirement{It must be possible to ``fix'' particular values of the
+  line profile parameters, so that only subset of lines or (say) the
+  width of a line is fit.}{1}
+
+\requirement{The software should allow hooks for line profile shapes
+other than Gaussian to be added in the future, possible user
+specified.}{2}
+
+%\makenote{Should it be possible to attach multiple sets of fits to the
+%data (similar to CL tables in classic AIPS), so the user can
+%experiment with different ways of fitting the data?}
+
+%\makenote{Should calculations of rotational temperatures etc be
+%handled when fitting hyperfine components, or should the user be doing
+%this themselves?}
+
+\subsection{Calibration}
+
+The software should handle all basic system temperature (Tsys) and
+gain calibration as well as opacity corrections where relevant. The
+Tsys value should be contained in the rpfits files. The actual
+application of the T$_{\mbox{sys}}$ factor will be applied as part of
+the sky subtraction ($\S$\ref{sec:skysubtraction}). The units of Tsys
+recorded in the data may be either in Jy or Kelvin, which will affect
+how the data is calibrated. The rpfits file does {\em not} distinguish
+if the flux units are Kelvin or Janskys. 
+
+\requirement{Gain elevation corrections should be implemented using a
+elevation dependent polynomial. The polynomial coefficients will be
+telescope and frequency dependent. They will also have a (long term)
+time dependence.}{1}
+
+\requirement{The user may wish to supply their own gain polynomial.}{2}
+
+\requirement{When required by the user, the spectral units must be
+converted from Kelvin to Jansky. At higher (3mm) frequencies this
+conversion is often not applied. The conversion factor is\medskip
+\reqeqn{\mbox{Flux (Jy)} = \frac{T \times 2 k_b \times
+10^{26}}{\eta A}},\medskip\\where $k_b$ is Boltzmann's constant, A is
+the illuminated area of the telescope and $\eta$ is the efficiency of
+the telescope (frequency, telescope and time dependent)}{1}
+%%TODO%% Use planks formula here?
+
+\requirement{In some cases the recorded Tsys values will be
+wrong. There needs to be a mechanism to scale the Tsys value and the
+spectrum if the Tsys value has already been applied (i.e. a simple and
+consistent rescaling factor).}{2}
+
+\requirement{The data may need to be corrected for opacity effects,
+particularly at frequencies of 20~GHz and higher. The opacity factor
+to apply is given by\medskip\reqeqn{C_o = e^{\tau/cos(z)}}\medskip\\
+where $\tau$ is the opacity and z is the zenith angle (90-El). These
+corrections will generally be derived from periodic ``skydip''
+measurements. These values will not be contained in the rpfits files,
+so there should be a simple way of the software obtaining them and
+interpolating in time (the user should not {\em have} to type them in,
+but may want to). Reading in an ascii file which contains the skydip
+data along with a time-stamp would be one possibility.}{1}
+
+\requirement{For wideband, multibit observations, the software should
+have the option to handle Tsys which varies across the band. The exact
+implementation will have to be decided once experience is gained with
+the new Mopra digital filterbank. This will affect the sky subtraction
+algorithms (requirement \reqref{ref:skysub}).}{2}
+
+%\makenote{Is the dependence of gain on frequency weak enough for one
+%set of coefficients for each receiver, or is a full frequency dependent
+%set of values needed?}
+
+%\makenote{Should it be possible to read ``correct'' Tsys values from
+%an external ascii file?} 
+
+\subsection{Editing \& RFI robustness}
+
+In a data set with many observations, individual spectra may be
+corrupted or the data may be affected by RFI and ``birdies''. The user
+needs to be able to easily flag individual spectra or channels. This
+may affect other routines such as sky-subtraction, as this will
+disrupt the reference/source sequence.
+
+\requirement{The user must be able to set an entire spectra or part
+thereof (individual polarisation, IF etc) as being invalid. The
+effected channels should either be blanked or interpolated depending
+on the user wishes. When blanked data is plotted, the plotting routine
+should also either interpolate the data on the fly or show a blank in
+the spectrum, depending on the users preferences.}{1}
+
+\requirement{The user must be able to indicate an individual spectral
+point or range of spectral points are invalid. This should be applied
+to an individual spectra, or set of spectra.}{1}
+
+\requirement{The user should be able to plot the average spectral flux
+across the band, or part of the band, as a function of time and
+interactively select sections of data which should be marked as
+invalid (similar to IBLED in classic aips).}{3}
+
+\requirement{Where relevant, fitting routines etc should have the
+option of selecting RFI tolerant (``robust'') algorithms. This will
+require investigating alternate fitting routines other than the
+least-squares approach.}{3}
+
+\requirement{A routine to automatically find birdies or RFI corrupted
+data and indicate the data as invalid would be useful.}{3}
+
+\requirement{Other routines must be able to cope with portions of data
+which are marked as invalid.}{1}
+
+\subsection{Spectra mathematics and manipulation}
+
+A flexible suite of mathematical operations on the spectra should be
+possible. This should include options such as adding, subtracting,
+averaging and scaling the data. For common operations such as
+averaging and smoothing, it must be simple for the user to invoke the
+function (i.e. not to have to start up a complex spectral
+calculator). Where it makes sense, it should be possible to manipulate
+multiple spectra simultaneously.
+
+The spectral manipulations which should be available are:
+
+\requirement{Add or subtract multiple spectra.}{1}
+
+\requirement{Averaging multiple spectra, with optional weighting based
+on Tsys, integration or rms. If the velocity of the spectra to be
+averaged is different, the data should be automatically aligned in
+velocity.}{0}
+
+\requirement{Various robust averaging possibilities (e.g. median
+averaging, clipped means etc) should be possible.}{2}
+
+\requirement{Re-sampling or re-binning of the data to a lower (or
+higher) spectral resolution (i.e. change the number of spectral
+points). The re-sampling factor may not necessarily be an integer.}{2}
+
+\requirement{It must be possible to shift the data in
+  ``frequency/velocity''. This should include channel, frequency and
+  velocity shifts of an arbitrary amount.}{1}
+
+\requirement{Spectral smoothing of the data. Hanning, Tukey, boxcar
+and Gaussian smoothing of variable widths should be possible.}{1}
+
+\requirement{Scaling of the spectra.}{1}
+
+\requirement{Calculate basic statistical values (maximum, minimum,
+rms, mean) on a range of spectral points. The range may not be
+contiguous. The calculated rms value should be retained with the
+spectra so it can be optionally used for weighted averaging of
+spectra.}{1}
+
+\requirement{It must be possible to calculate the flux integral over a
+range of channels. The units should be Jy.km/s (or Kelvin.km/s). The
+channel range for the calculation should be specific via the GUI or
+CLI.}{2}
+
+\requirement{It must be possible to calculate the numerical ``width''
+of a line (full width at half maximum type measurement). This should
+be calculated by specifying a channel range and finding the maximum
+value in this range and then finding the interpolated crossing point
+of the data as a user defined fraction of the maximum (default
+50\%). The profile width and velocity mid-point should then be
+computed. If the profile shape is complex (e.g. double arch) with
+multiple crossing points of the fraction value, the minimum and
+maximum width values should be calculated. There should be the option
+of using a user specified ``maximum value''.}{2}
+
+\requirement{The user must be able to easily change the rest-frequency
+to which the velocity is referenced.}{1}
+
+\requirement{FFT filtering for high- and lowpass filtering and
+tapering.}{3}
+
+\requirement{It should be possible to FFT the data to and from power
+  spectra to the autocorrelation function.}{2}
+
+\requirement{The user may wish to compute the cross correlation
+function of two spectra. The result should be a standard ``spectra'',
+which can be displayed and analysed using other functions (max, rms
+etc).}{3}
+
+\requirement{Complex experiment specific processing can often be done
+using a series of the simple of basic functions. A spectral calculator
+options should be added to the CLI to perform a series of
+manipulations on a set of spectra.}{3}
+
+The user may want to perform specific analysis on the data using the
+functionality above, but wish to do the manipulation between two
+polarisations or IFs. Allowing the functions to also, optionally,
+specify specific polarisations or IF would be an implementation and
+interface nightmare. The simplest solution is the allow the data to be
+``split'' into separate spectra.
+
+\requirement{It must be possible to take multi IF, multibeam or
+polarisation data and split out the individual spectral portions to
+form self contained spectra.}{2}
+
+\subsection{Polarimetry}
+
+The software must fully support polarmetric analysis. This includes
+calibration and basic conversions. Observations may be made with
+linear or circular feed and the backend may or may not compute the
+cross polarisation products. As such the software must cope with a
+variety of conversions. The software should be able to calculate
+stokes parameters with or without solving for leakage terms.
+
+%\makenote{It is debatable whether stokes I is the sum or average or
+%two dual polarisation measurements.}
+
+\requirement{All functions on the data (calibration, sky subtraction
+spectral mathematics) must support arbitrary, multiple, polarisation
+(linear, circular \& stokes and single, dual \& cross
+polarisations.}{1}
+
+\requirement{It must be possible to calculate stokes I from single or
+dual polarisation observations.}{1}
+
+\requirement{Average a mixture of dual polarisation and single
+polarisation data and form average stokes I (e.g. for a long
+observation of a source, in which one polarisation is missing for some
+time.}{3}
+
+\requirement{Full stokes parameters should be obtained from dual pol
+(linear or circular) observations where the cross polarisation
+products have been calculated.}{1}
+
+%\requirement{If the observations used linear polarisations and the
+%cross polarisations were not computed, the source needs to be
+%observed with the feeds set at least 3 different parallactic angles
+%(note that if dual linear feeds are available, 2 orthogonal
+%parallactic angles are obtained at once). The Stokes parameters can be
+%solved using a least squares fit to the equation:
+%\reqeqn{Iu/2 + Ip * cos^2 (PA + p)},\\ 
+%where PA is the linear feed position angle, p is the polarisation
+%angle, Iu and Ip and the unpolarised and linearly polarised
+%intensity. {\em Stolen from SPC. I need to write this in more useful
+%language. Is this technique likely to be used anymore?.}}{3}
+
+\requirement{If dual circular polarisation measurements are taken,
+without computing the cross products, the software should still be
+able to compute stokes I and V.}{2}
+
+\requirement{The software should be able to calculate leakage terms
+from a calibrator source and correct the data either before or after
+conversion to Stokes. (ref. Johnston, 2002, PASA, 19, 277)}{3}
+
+\requirement{The software should be able to determine absolute
+position angle from a calibrator source and correct the data either
+before or after conversion to Stokes.}{3}
+
+\requirement{Zeeman splitting factors should be derived from
+(previous) profile fitting and the left and right circular
+polarisations. The velocity shift varies linearly with the magnetic
+field, but the scaling factor depends on the molecule and
+transition. Scaling factor for common transitions should be known by
+the software and the user able to enter factors for less common
+transitions. Correctly identifying Zeeman pairs is crucial in getting
+the correct result. The software should attempt to make an initial
+guess of pairs (based on component velocity and width) but make the
+user confirm and override the pairing if required.}{3}
+
+\subsection{Data Selection}
+While the software is running the user will usually have loaded
+multiple (possibly many) spectra each of which may have multiple IFs,
+data from multiple beams and multiple polarisations. The user will
+want to be able to quickly flip from considering one spectra to
+another and, where relevant, want to perform parallel processing on
+multiple spectra at once (e.g. baselining a sequence of on/off
+observations of the same source which will later be averaged
+together).
+
+\requirement{The software needs an easy-to-use mechanism to select
+either individual or multiple spectra for viewing, parallel processing
+etc.}{1}
+
+\requirement{An easy-to-use mechanism to select individual IFs, beams
+or polarisations is needed.}{1}
+
+\requirement{\label{ref:chansel} The range of spectral points to use
+for baseline removal, statistical calculations, RFI editing, analysis
+etc must be easily set by the user from both the CLI and GUI. From the
+CLI there must be the option of setting the range using a variety of
+units (channel number, velocity, frequency). The selection range will
+probably not be a contiguous set of channels, but many sets of
+disjoint channel ranges. For some tasks (such as baseline subtraction
+and statistical values), the channel range should be retained and be
+available as a plot overlay.}{1}
+
+\requirement{When performing baseline subtraction on many spectra
+simultaneously, the software should have a mechanism for identifying
+``on'' and ``off'' spectra and automatically selecting the signal and
+quotient spectra. The algorithm needs to cope with on/off/on/off
+sequences as well as off/on/on/off. If an individual quotient spectra
+has been marked as invalid, an alternative should be found.}{3}
+
+\requirement{The software should be able to select sets of sources
+based on simple regular expression type filtering (wild cards) on a
+range of header values. Examples include G309$*$ or G309$*$w to select
+on source name, or NH3$*$ to select on molecule name.}{3}
+
+\subsection{Plugins}
+
+\requirement{It would be desirable to support ``plugins'', user
+definable functions for specific processing. The plugin code must have
+full access (read/write) to the spectra data and headers. It should
+also be possible to create new spectra which the software treats the
+same as the original data. Preferably some (limited) access to the
+plotter would also be possible and a number of the standard functions
+accessible as ``library'' routines.}{3}
+
+\subsection{Pipelining}
+
+\requirement{Some sort of pipelining mode is required. This would
+involve doing a quotient spectra, applying appropriate calibration and
+possibly fitting a Gaussian to any lines present.}{3}
+
+\subsection{Methanol Multibeam Survey}
+
+The software may need to support reduction of data from the methanol
+multibeam project. If so the pipelining will need to be flexible and
+powerful enough to support this.
+
+\subsection{Miscellaneous functionality}
+
+\requirement{The software should be able to take a simple ``grid'' of
+observations (normally a set of observations in a cross pattern on the
+sky) and, for a subset of channels, fit the position of the
+emission. The fit positions should be either plotted on the screen or
+exported in a simple ascii form.}{3}
+
+\requirement{The kinematic distance of a source should be calculated
+using basic Galactic rotation models. Multiple Galactic rotation
+models must be supported and a mechanism for easily adding more.}{3}
+
+\requirement{For 1420 MHz observations of HI, the rms (Tsys) values
+vary significantly across the band. The software should be able to
+compute the rms as a function of frequency across the spectra from the
+off-pulse data and then be able to plot n-sigma error bars on the
+spectra.}{3}
+
+\requirement{It should be possible to take a selection of calibrated
+spectra which are then passed to the ``Gridzilla'' program to produce
+an image cube. Analysis of this cube would be done using external
+programs (e.g. Miriad, aips++)}{3}
+
+\section{Help}
+
+\requirement{There should be built-in web-based documentation, which
+can be easily kept up-to-date}{1}
+
+\requirement{A short and simple end-to-end cookbook for basic data
+analysis should be available.}{1}
+
+\section{Data and meta-data}
+
+\requirement{The software must be capable of handling multi-IF
+  (potentially dozens of IFs) and multi-beam data with arbitrary
+  polarisation (e.g. single pol, dual pol, full stokes etc).}{1}
+
+\requirement{The software should handle pulsar binned data for pulsar
+absorption experiments.}{3}
+
+\subsection{History}
+
+\requirement{A user viewable history of data processing steps should
+be kept as part of the data. Where possible this should be retained
+when data is imported from other packages.}{1}
+
+\requirement{It should be possible to use the history information to
+create template pipeline scripts for batch processing.}{2}
+
+\subsection{Multiple IFs}
+
+\requirement{If multiple IFs are present (currently Tidbinbilla can
+produce two IFs and the new wideband spectrometer for Mopra may have
+dozens of IFs), the software should handle the data
+transparently. Potentially each IF may have a significantly different
+sky frequency and be observing a different molecule or transition with
+a different rest frequency. From the users point of view,
+simultaneously obtained IFs should be kept within the same
+``container'' (not split into a myriad of separate ``container'').}{1}
+
+%\makenote{Does the case of multiple lines (so multiple
+%rest frequencies) within a single IF need special attention?}
+
+\subsection{Multibeam}
+
+\requirement{Basic handling of multibeam data should be possible (ie
+in general each beam will be treated as a separate observation, but
+all within the same container. The user should be able to view or
+process either individual beams or all beams in parallel.}{2}
+
+\requirement{The use of a single beam observing a source and the
+rest of the beams as reference beams for sky-subtraction should be
+investigated.}{2}
+
+\subsection{Robust fitting}
+
+\requirement{If robust fitting using median filtering is used, then
+the individual integrations from the observations should {\em not} be
+averaged when the data is imported, but retained within a single
+container. Inspection of this data should be optionally of the averaged
+or individual data.}{2}
+
+\subsection{Fit parameters}
+
+\requirement{The fitting parameters for functions which have been fit
+to the data (e.g. for baseline removal or Gaussian fits) should be
+retained as an integral part of the data and stored permantely on
+disk.}{1}
+
+\requirement{It must be possible to export fitting values in an
+appropriate form. (i.e. ascii text format)}{1}
+
+\requirement{It should be possible to ``undo'' functions which have
+been subtracted from the data (e.g. baseline polynomials).}{3}
+
+\subsection{Coordinate frames and units}
+
+\requirement{Coordinate frames and unit selection and handling needs
+to be flexible and relatively transparent to the user (i.e. if the
+users preference is for LSRK velocities, they do not need to worry
+about the reference frame in which the data was observed).}{1}
+
+\requirement{At a minimum the following reference frames and
+conventions should be handled: \setlength{\parindent}{0pt}
+
+\smallskip
+\anitem{Position}{(RA,Dec) in J2000 \& B1950 (as well as other
+  arbitrary epochs), Galactic, (Az,El).}
+
+\anitem{Frequency}{Velocity (Topocentric, Geocentric, Barycentric,
+    Heliocentric, kinematical LSR, dynamical LSR, Rest), Frequency
+    (MHz, GHz), channel number.}
+
+\anitem{Velocity}{ Optical, Radio, Relativistic.}
+
+\anitem{Flux}{ Jansky, Kelvin (mJy etc).}}{1}
+
+\requirement{All data should be internally labelled with the
+  appropriate coordinate frame and units. If this information is
+  ambiguous for some reason, it should be set when the data is imported
+  and the user should not have to worry about it again.}{1}
+
+\requirement{It should be clear to the user what coordinate frame
+(velocity, position etc) the data is being presented as.}{1}
+
+\subsection{Meta-data}
+
+A comprehensive set of header data should be read from the input data
+files. In general all meta-data available in the rpfits file should be
+retained. The user may wish to enter some specific values by hand.
+
+\requirement{All header data should be viewable and editable by the
+user. This includes changes such as scaling the given Tsys values.}{2}
+
+\requirement{Missing header data should be handled gracefully,
+i.e. the software should fill the values with ``blanks'' and be able
+to continue to process the data if possible.}{1}
+
+\requirement{The user must be able to add missing header data, which
+is not present in the RPFITs file. It must be possible to add the same
+header data to multiple scans simultaneously.}{2}
+
+\extendedrequirement{
+The following  header data would be required per scan:
+\begin{itemize}
+  \item Source name
+  \item Scan type (signal or reference)
+  \item Integration time
+  \item Scan length (actual time of observation, $\ge$ integration time)
+  \item Telescope
+  \item UT time and date of observation
+  \item Telescope elevation of observation
+  \item Parallactic angle
+  \item Beam size
+  \item Scan ID
+  \item Observer
+  \item Project
+  \item Polarisation
+  \item Receiver
+  \item Telescope coordinates
+  \item Weather info (temperature, pressure, humidity)
+  \item LO chain setup
+  \item User axis display preference (LSR velocity, frequency etc).
+\end{itemize}
+}{1}
+
+\extendedrequirement{
+\label{req:if}
+The following header data is required for each IF, beam etc:
+\begin{itemize}
+\item Source coordinates and coordinate frame
+\item Frequency/velocity axis definition and type
+\item System Temperature
+\item Antenna gain (if Tsys measured in Kelvin)
+\item Beam number
+\item Molecule rest frequency$^\dagger$
+\item Molecular name$^\dagger$
+\item Molecular transition$^\dagger$
+\item Molecular formula$^\dagger$
+\end{itemize}
+}{1}
+
+\requirement{The molecular formula could be stored with embedded
+superscripted and subscripted symbols for ``pretty'' printing on the
+plotted, but printed in plain text on the CLI or in ascii output}{3}
+
+Some molecular line rest-frequencies are close enough that two or more
+molecules or transitions may be observed in a single IF. Typical
+examples include the 1665/1667~MHz OH maser pair, NH$_3$ transitions,
+and many observations in the 3~mm band.
+
+\vspace{\parskip}
+\requirement{The software should optionally support multiple lines per
+IF, by storing a set of rest frequencies per IF, rather than a single
+value. The header values in requirement \reqref{req:if} marked with a
+$\dagger$ would all have to be stored as an array of values rather
+than a scalar. A simple mechanism must be posible to change the
+currently ``active'' rest-frequency.}{3}
+
+\section{Installation}
+
+\requirement{It must be possible for astronomers to install the
+software at their own institute with either a moderate amount of OS
+experience or some help from the local system administrators. This
+includes installation on a central ``NFS'' server as well as local
+desk-tops.}{1}
+
+\requirement{The software must run on Solaris and all major flavours
+of Linux (Fedora/Redhat, Debian, etc).
+ }{1}
+
+\requirement{It must be possible for users to
+install the software on their (unix) laptops and run with no network
+connection. }{1}
+
+\requirement{It should be relatively easy to upgrade to the lastest
+version of the software.}{2}
+
+\requirement{The software should run on MacOS/X}{3}
+
+\requirement{It would be desirable for the software to run on
+Windows.}{3}
+
+\section{Known Issues}
+\label{sec:issues}
+The following issue are known problems with the data from ATNF
+telescopes, which probably should be automatically corrected for if at
+all possible. The best place to do this is while loading the data.
+
+\subsection{General}
+
+\begin{itemize}
+  \item All polarisations in the RPFITS files are labelled as
+  XX/YY. These need to be relabelled as LL/RR when appropriate.
+\end{itemize}
+
+\subsection{Mopra}
+
+\begin{itemize}
+\item Data obtained in 2002 \& 2003 (and probably before) have an
+  error in the frequency headers (this may be corrected by an external
+  program). \makenote{Nedd Ladd}
+
+\item The (RA,Dec) positions in the data file are in date coordinates
+  not J2000. This causes problems for packages like Class when
+  averaging the data. \makenote{Maria Hunt}
+
+\item It is possible Tsys calibration is inconsistent currently.
+  \makenote{Cormac Purcell??}
+
+\end{itemize}
+
+\subsection{Parkes}
+
+\begin{itemize}
+\item For pulsar data the automatic gain control is disabled. This
+means the nominal Tsys measurement does not change and Tsys per
+integration is encoded in a non-standard way. \makenote{Simon
+Johnston}
+\end{itemize}
+
+\subsection{Tidbinbilla}
+
+\begin{itemize}
+\item All 20-GHz data is calibrated in flux units of Kelvin.
+\end{itemize}
+
+\section{Acknowledgements}
+
+We would like to thank the following people for input directly or from
+previous documents: Maria Hunt, Paul Jones, Jim Caswell \& Dave
+McConnell, Frank Briggs, Juergen Ott, Daniel Pisano, Jim Lovell, Ray
+Norris, WIm Brouw, Maxim Voronkov, Vincent McIntyre, Jessica Chapman,
+Simon Johnston, Nedd Ladd, Lister Staveley-Smith, Cormac Purcell \&
+Rachel Deacon.
+
+\end{document}
Index: tags/Release2.1.0b/admin/requirements2.tex
===================================================================
--- tags/Release2.1.0b/admin/requirements2.tex	(revision 1196)
+++ tags/Release2.1.0b/admin/requirements2.tex	(revision 1196)
@@ -0,0 +1,1359 @@
+\documentclass[11pt]{article}
+\usepackage{a4}
+\usepackage{calc}
+\usepackage{ifthen}
+\usepackage{smartref}
+\usepackage{longtable}
+
+\def\complete{no}
+
+% Adjust the page size
+\addtolength{\oddsidemargin}{-0.4in}
+\addtolength{\evensidemargin}{+0.4in}
+\addtolength{\textwidth}{+0.8in}
+
+\setlength{\parindent}{0mm}
+\setlength{\parskip}{1ex}
+
+
+\title{ASAP - ATNF Spectral Analysis Package\\
+  Software Requirements - Development Cycle 2 }
+ \author{Chris Phillips \& Malte Marquarding}
+
+\newcounter{requirement}
+\newcounter{subrequirement}
+
+\addtoreflist{requirement}
+\newcommand{\reqref}[1]{R\ref{#1}-\requirementref{#1}}
+
+\newcommand{\makenote}[1]{{\bf \tt \em#1}}
+
+\newcommand{\anitem}[2]{\smallskip \parbox[t]{2cm}{#1}%
+  \parbox[t]{\textwidth-2cm}{#2}}
+
+\newcommand{\showreqcounter}{
+  R\arabic{section}.\arabic{subsection}-\arabic{requirement}
+}
+
+\newcommand{\showsubreqcounter}{
+  R\arabic{section}.\arabic{subsection}-\arabic{requirement}.\arabic{subrequirement}
+}
+
+\newcommand{\status}[2]{
+  \ifthenelse{\equal{#1}{Started}}{Started \hspace*{1cm} {\em Priority #2}}
+ {\ifthenelse{\equal{#1}{Not started}}{Not Started\hspace*{1cm} {\em Priority #2}}
+ {\ifthenelse{\equal{#1}{Done1}}{Completed}
+ { \ifthenelse{\equal{#1}{Duplicate}}{Duplicate?}
+ {#1}
+ }}}
+}
+
+% Also Deferred, Obsolete
+
+% Requirement command
+%  Summary
+%  Status
+%  Priority
+%  Time estimate
+%  Percent complete
+%  Completion date
+%  Text
+
+%\newarray\Requirements
+
+%\newcounter{numreq}
+
+\newcommand{\requirement}[7]{
+  \setcounter{subrequirement}{0}
+  \stepcounter{requirement}
+
+  \ifthenelse{\equal{\complete}{yes}}
+	     {\dorequirement{#1}{#2}{#3}{#4}{#5}{#6}{#7}}
+	     {\ifthenelse{\equal{#2}{Done1} 
+		 \or \equal{#2}{Deferred}
+		 \or \equal{#2}{Removed}
+		 \or \equal{#2}{Obsolete}}{}
+	       {\dorequirement{#1}{#2}{#3}{#4}{#5}{#6}{#7}}}
+}
+
+\newcommand{\dorequirement}[7]{
+  \hspace*{2mm}\begin{minipage}{\textwidth-2mm}
+    \setlength{\parindent}{-2mm}
+      {\bf \showreqcounter\ \bf #1} \\ 
+    #7 \\
+    \hspace*{1cm} \status{#2}{#3}
+  \end{minipage}
+
+  \typeout{REQUIREMENT: \showreqcounter & #1 & #2 & #3 & #4 & #5 & #6 :ENDREQ}
+}
+
+\newcommand{\subrequirement}[7]{
+  \stepcounter{subrequirement}
+
+  \ifthenelse{\equal{\complete}{yes}}
+	     {\dosubrequirement{#1}{#2}{#3}{#4}{#5}{#6}{#7}}
+	     {\ifthenelse{\equal{#2}{Done1} 
+		 \or \equal{#2}{Deferred}
+		 \or \equal{#2}{Obsolete} 
+		 \or \equal{#2}{Removed}}{}
+	       {\dosubrequirement{#1}{#2}{#3}{#4}{#5}{#6}{#7}}}
+}
+
+\newcommand{\dosubrequirement}[7]{
+  \hspace*{2mm}\begin{minipage}{\textwidth-2mm}
+    \setlength{\parindent}{-2mm}
+    {\bf \showsubreqcounter\ \bf #1} \\ 
+    #7 \\
+    \hspace*{1cm} \status{#2}{#3}
+  \end{minipage}
+
+  \typeout{REQUIREMENT: \showsubreqcounter & #1 & #2 & #3  & #4 & #5 & #6 :ENDREQ}
+}
+
+
+\newcommand{\extendedrequirement}[7]{
+  \setcounter{subrequirement}{0}
+
+  \hspace*{2mm}\begin{minipage}{\textwidth-2mm}
+    \setlength{\parindent}{-2mm}
+    \stepcounter{requirement}
+    {\bf \showreqcounter\  #1}
+    #7 
+    \hspace*{1cm} \status{#2}{#3}
+  \end{minipage}
+
+  \typeout{REQUIREMENT: \showreqcounter & #1 & #2 & #3 & #4 & #5 & #6 :ENDREQ}
+}
+
+
+\newcommand{\reqeqn}[1]{\\\hspace*{1cm} $#1$}
+
+\let\oldsection\section
+\renewcommand{\section}[1]{\setcounter{requirement}{0}\oldsection{#1}}
+
+\let\oldsubsection\subsection
+\renewcommand{\subsection}[1]{\setcounter{requirement}{0}\oldsubsection{#1}}
+
+\begin{document}
+
+\maketitle
+
+
+%\tableofcontents
+%\newpage
+
+\section{Introduction}
+
+ASAP has been written to replace the venerable single-dish software
+{\tt spc} for processing of single dish spectral line data from all
+ATNF observatories. Version 1.0 of ASAP was released in March
+2005. This document reflects an update of the initial requirements
+document. Some new requirements have been added and the requirement
+priorities have been reassessed for the next development cycle.
+
+\section{Scope}
+
+ASAP should be able to process all spectral line single-dish
+observations from ATNF telescopes (Parkes, Mopra \& Tidbinbilla). This
+includes reading the data produced by the telescope, calibration and
+reduction of the data and basic analysis of the data such as fitting
+line profiles etc.
+
+It has been assumed that the following processing is out of the scope
+of ASAP.
+\begin{itemize}
+\item Raster or ``on-the-fly'' mapping (This is handled by
+``livedata'' and gridzilla).
+\item Very complex or specific data processing. (A route into
+  Class\footnote{Part of the GLIDAS software package, produced by
+  Institut de Radio Astronomie Millime\'trique http://www.iram.fr}
+  should be available for advanced processing).
+%%TODO%% give example
+\item Continuum data.
+\item Pulsar timing observations.
+\end{itemize}
+
+\section{Priorities}
+
+Requirements have been given a value of 1 to 3. The other requirements
+will be implemented mainly depending on priority, with ``1'' the
+highest. Priority 3 and some priority 2 requirements will probably not
+get implemented in the duration of the second development cycle.
+
+\section{User Interface}
+
+The user interface (UI) is the most important part of a single dish
+processing package, but probably the most difficult to get right. Long
+term the UI for this software will consist of three parts.
+\begin{itemize}
+  \item A graphical user interface (GUI).
+  \item An interactive command line interface (CLI).
+  \item A scriptable interface for batch processing.
+\end{itemize}
+
+The CLI and scriptable interface are essentially be the same.
+
+The software does not {\em need} to be able to run solely from a
+``vt100'' style terminal. It can be assumed that the user is running
+the software from within a windowed (i.e. X11) environment. This will
+mean it will not necessarily be possible to run the software remotely
+over a slow network connection (e.g. internationally or from home).
+Where possible, operations on the data should be possible from all
+three aspects of the user interface. 
+
+The user interface needs to be implemented so that the user can easily
+and transparently work on spectra either one at a time or by
+processing multiple spectra in parallel. This means there must be an
+easy way to select specific or multiple spectra to display or process.
+
+At this stage the development of a GUI has been deferred until the
+basic package has stabilised and most features have been
+implemented. At that stage a decision will be made on how to best
+implement a GUI. On a shorter timescale specific purpose GUIs (such as
+a simple Wizard for processing standard Mopra data) may be produced on
+an as-needed basic.
+
+\subsection{Graphical User Interface}
+
+At this stage a general ASAP GUI has been deferred to a later date.
+
+\smallskip
+
+\requirement{Simple interface}{Deferred}{}{}{}{}{It should be simple,
+intuitive and uncluttered. Specifically, use of many windows
+simultaneously should be discouraged, as should hiding functionality
+behind layers of dialog boxes.}
+
+\requirement{Integrated plotter}{Deferred}{}{}{}{}{The plotting window
+should be a major component of the GUI control, not a separate
+isolated window.}
+
+\requirement{Minimal controls}{Deferred}{}{}{}{}{The interface should use
+  minimal ``always visible'' controls, with use of pull down menus and
+  maybe a toolbar for frequency used functions. }
+
+\requirement{Keyboard shortcuts}{Deferred}{}{}{}{}{Keyboard shortcuts should
+be available.}
+
+\requirement{GUI user preferences}{Deferred}{}{}{}{}{Most user preferences
+(i.e. keywords in the CLI) should be presented in a popup, tabbed,
+dialog box.}
+
+\requirement{GUI line fitting}{Deferred}{}{}{}{}{When performing line
+profile fitting, a spreadsheet type window should be viewable which
+shows the current parameter values (amplitude, velocity etc) for each
+line fitted and allow the user to change these parameters or set the
+current value as fixed. This GUI should stay synchronised with any CLI
+changes to these values.}
+
+\requirement{Mopra online GUI}{Not started}{1}{}{}{} 
+{A simple GUI to do very basic online reduction of Mopra data should
+be available. This should include averaging data, quotient }
+
+\subsection{Command Line Interface}
+
+The command line interface is the main user interface to ASAP. It is
+implemented in ipython using a objected oriented command approach.
+
+\requirement{Virtual CLI}{Obsolete}{}{}{}{}{While the GUI should be the main
+interface for new users and for basic manipulation, some tasks can be
+more efficiently performed using a CLI. A virtual CLI could be
+integrated as part of the GUI.}
+
+\requirement{CLI keyword/argument}{Obsolete}{}{}{}{}{The CLI should have a
+keyword/argument form and never prompt the user for specific values
+(the user should be able to change values which are retained until
+they wants to change them again).}
+
+\requirement{CLI case insensitive}{Obsolete}{}{}{}{}{The CLI should be case
+insensitive and accept minimum matching and short forms of
+keywords.}
+
+\requirement{CLI available routines}{Done1}{}{}{}{}{The user must be able to
+quickly and easily see from the command line the available routines
+and keywords which affect it, so they can see which parameters may
+need changing.}
+
+\subsection{Scripting}
+
+\requirement{Scripting}{Done1}{1}{}{}{}{It must be possible to run the
+software in a scripting mode. This would be to process large amounts
+of data in a routine manner and also to automatically reproduce
+specific plots etc (So the scripting must have full control of the
+plotter). Preferably the scripting ``language'' and the CLI would be
+the same.}
+
+\requirement{Standard scripts}{Not started}{1}{}{}{} {ASAP should come
+with sample scripts which users can use for standard observing modes
+(e.g. standard Mopra observing, Parkes polarimetry etc)}
+
+%\requirement{Scripts from History}{Duplicate}{}{}{}{}{It would be worthwhile
+%having a method to auto-generate scripts (for reduction or plotting)
+%from current spectra history, or some similar method.}
+
+\section{Plotter}
+
+The plotter should be fully interactive and be an integral part of the
+GUI and software interface.
+
+\requirement{High quality plots}{Done1}{}{}{}{}{It must be able to
+produce plots of publishable quality.}
+
+\subrequirement{Histogram plots}{Not started}{1}{0.5d}{}{} {As well as line
+plots, there needs to be an option to plot spectra in ``Histogram''
+mode}
+
+
+The user must be able to specify:
+
+\subrequirement{Line Thickness}{Started}{1}{0.1d}{}{}{}
+
+\subrequirement{Character size}{Not started}{1}{0.1d}{}{}{}
+
+\subrequirement{Colours}{Started}{1}{0.75d}{}{}{}
+
+\subrequirement{Line styles}{Not started}{1}{0.5d}{}{}{}
+
+\subrequirement{Position of axis ticks}{Done1}{2}{}{}{}{}
+
+\subrequirement{Hard Copies}{Done1}{1}{}{}{}{Producing hard copies
+in postscript and .png format. Other formats may be added on an as
+need basic.}
+
+\subrequirement{Non-interactive hard copies}{Not started}{1}{2d}{}{} 
+{It must be possible to produce hard copiers without an interactive
+(i.e X11) plotter starting}.
+
+\subrequirement{Scriptable plotting}{Done1}{1}{}{}{} {All aspects of
+the plotter (zooming etc) must be setable from the command line for
+scripting}
+
+\requirement{Arbitrary plots}{Not started}{3}{}{}{}
+{It must be possible to flexibly select the data to plot (e.g. Tsys vs
+time etc as well as plots such as amplitude vs channel number or
+velocity). Preferably any of the header values for a selection of
+scans could be plotted on a scatter plot (e.g. Tsys vs elevation)}
+
+\requirement{Overlay spectra}{Done1}{}{}{}{}{It must be possible to overlay
+  multiple spectra on a single plot using different colours and/or
+  different line styles. (Including multiple stokes data and multiple
+  IFs).[[CHECK]]}
+
+\requirement{Plot individual spectra}{Done1}{}{}{}{}{It must be possible to
+  plot either the individual integrations (in either a stacked
+  fashion, or using a new subplot per integration)}
+
+\subrequirement{Auto-average integrations for plotting}{Not started}{2}{}{}{}
+{It should be possible to optionally auto-average integrations of a
+scan for plotting (for data thats has not already been scan averaged)}
+
+\requirement{Plotter multi-panelling}{Done1}{1}{}{}{}
+{It must be possible to multi-panel spectra in an n$\times$m size
+grid. It must be possible to easily change the number of plots per
+page, ie define the ``n'' and ``m'' values.}
+
+\subrequirement{Step between plots}{Not started}{2}{}{}{}
+{If more spectra than can fit on the plot matrix are to be plotted,
+then it must be possible to step back and forth between the viewable
+spectra (i.e. ``multi-page'' plots). This includes stepping through a
+single plot on the pages at a time.}
+
+\requirement{Multi-panel: change \# panels}{Not started}{2}{}{}{} 
+{When using multi-panelling, the plotter should automatically update
+the plot when the plot matrix dimensions (``n'' and ``m'' are changed)}
+
+\requirement{Plotter interactive zooming}{Done1}{}{}{}{}{It must be possible
+  to interactively zoom the plot (channel range selection and
+  amplitude of the spectra etc.)  This includes both GUI control of
+  the zooming as well as command line control of either the zoom
+  factor or directly specifying the zoom bounds. }
+
+\requirement{Zoomed subplot}{Not started}{2}{}{}{}
+{On a single plot, it should be possible to plot the full spectrum and
+a zoomed copy of the data (using a different lie style) to see weak
+features. The user must be able to specify the zoom factor.}
+
+\requirement{Offset plots}{Not started}{2}{}{}{}{Optionally when stacking
+  multiple spectral plots in one subwindow, a (user definable) offset
+  in the ``y'' direction should be added to each subsequent
+  spectra.}
+
+\requirement{Plotter auto-update}{Not started}{3}{}{}{} 
+{The plotter should automatically update to reflect user processing,
+either from the CLI or GUI. The user should have to option to turn
+this feature off if they so wish.}
+
+\requirement{Waterfall plot}{Not started}{3}{}{}{}
+{It should be possible to plot individual integrations (possibly from
+multiple scans) in a ``waterfall'' plot. This is an image based
+display, where spectral channel is along the x-axis of the plot, time
+(or integration number) along the y-axis and greyscale or colour
+represent the amplitude of spectra. Interactive zooming and panning of
+this image should be supported. }
+
+\requirement{Waterfall editing}{Not started}{3}{}{}{} 
+{When plotting ``waterfall'' plots, it should be possible to
+interactively select regions or points and mark them as invalid
+(i.e. to remove RFI affected data). The plotter should also show the
+time/velocity of the pixel beneath the cursor.}
+
+\requirement{Export waterfall to FITS}{Not started}{3}{}{}{} 
+{It should be possible to export the ``waterfall'' plot images as a
+FITs file, for user specific analysis.}
+
+\requirement{Plot line catalog overlays}{Not started}{1}{3d}{}{} {Line markers overlays,
+read from a catalogue should be optionally available. This would
+include the full Lovas catalogue, the JPL line catalogue and radio
+recombination lines. The lines would be Doppler corrected to a
+specified velocity. The user must be able to plot just a sub-section
+of the lines in any specific catalogue (to avoid clutter).}
+
+\subrequirement{Plot user catalog overlays}{Not started}{2}{}{}{}
+{Simple user definable catalogue should be definable for plot overlays}
+
+\requirement{Plot fitted functions}{Done1}{}{}{}{}
+{Optionally plot fitted functions (e.g line profiles or baseline
+fit). If multiple components (e.g. Gaussian) have been fit, it should
+be possible to show the individual functions or the sum of the
+components}
+
+\requirement{Plot residual data}{Started}{1}{0.5d}{}{}
+{It should be possible to plot the residual data with or without
+subtraction of fit functions. This includes plotting the spectra with
+or without baseline removal and the residual after subtracting
+Gaussian fits. The default should be to plot the data with baseline
+subtracted but profile fits not subtracted.}
+
+\requirement{Plot header data}{Not started}{2}{}{}{} {Basic header data
+(source name, molecule, observation time, Tsys, elevation, parallactic
+angle etc) should be optionally shown, either on the plot or next to
+it. This may either consist of a set of values, or only one or two
+values the user specifically wants to see (source name and molecule,
+for example).}
+
+\subrequirement{User define header plot positions}{Not started}{3}{}{}{}
+{The user should be able to define where on the plot the header info
+would appear.}
+
+\requirement{Realtime cursor position}{Done1}{}{}{}{} 
+{Optionally, relevant data such as the current mouse position should
+be displayed (maybe with a mode to display an extended cross,
+horizontal or vertical line at the current cursor position).}
+
+\requirement{Plot annotations}{Not started}{2}{}{}{}{The user should be able
+  to define simple annotations. This would include text overlay and
+  probably simple graphics (lines, arrows etc).}
+
+The user must be able to use the plotter window to interactively set
+initial values and ranges used for fitting functions etc. The use of
+keyboard ``shortcuts'' or other similar ``power user'' features should
+be available to save the time of experienced users. 
+
+{\em The following requirements are deferred as they require a version
+of Matplotlib not yet available.}
+
+The plotter should be used to set the following values:
+
+\requirement{Interactive channel selection}{Deferred}{1}{}{}{}{Range of
+ spectral channels needed for specific tasks (ie the channel mask)
+ (See requirement \reqref{ref:chansel})}
+
+\requirement{Interactive line fitting}{Deferred}{1}{}{}{}{Initial
+Gaussian parameters (velocity, width, amplitude) for profile
+fitting.}
+
+\requirement{Plotter change fit values}{Deferred}{1}{}{}{}
+{Change the parameter values of existing line profile
+fits, or channel ranges used for baseline fits.}
+
+\section{Functionality}
+
+\subsection{Import/export}
+
+The software needs a set of import/export functions to deal with a
+variety of data formats and to be able to exchange data with other
+popular packages. These functions should be flexible enough to allow
+the user to perform analysis functions in an different package and
+re-import the data (or vice versa). The import function must be
+modular enough to easily add new file formats when the need arises.
+To properly import data, extra information may have to be read from
+secondary calibration files (such as GTP, Gated Total Power, for 3~mm
+wavelength data taken with Mopra). The import functions should be
+flexible enough to gracefully handle data files with missing headers
+etc. They should also be able to make telescope and date specific
+corrections to the data (for ATNF observatories).
+
+The software must be able to read (import) the following file formats.
+
+\requirement{Read rpfits}{Done1}{}{}{}{}{The rpfits file format produced by
+  all current ATNF correlators.}
+
+\requirement{Read sdfits}{Done1}{}{}{}{}{SDFITS (currently written by {\tt SPC}).}
+
+\requirement{Read simple FITS}{Not started}{2}{}{}{}{Simple ``image'' FITS
+(used by CLASS}
+
+\requirement{Read historic formats}{Not started}{3}{}{}{}
+{Historic ATNF single dish formats (Spectra, SPC, SLAP). Possibly a
+set of routines to translate these formats to SDFITs would suffice.}
+
+\requirement{Read PSRFITS}{Deferred}{}{}{}{}{PSRFIT for pulsar spectroscopy.}
+
+\requirement{Read online data}{Not started}{1}{0.1d}{}{}
+{For online analysis, the software should be able to read an rpfits
+file which is is still currently open for writing by the telescope
+backend processor.}
+
+\requirement{Handle Doppler data}{Done1}{1}{}{}{}{Data which has been
+observed in either a fixed frequency or Doppler tracked fashion needs
+to be handled.}
+
+The software should be able to export the data in the following formats.
+
+\requirement{Write SDFITS}{Done1}{}{}{}{}{Single Dish FITS.}
+
+\requirement{Write simple FITS}{Done1}{}{}{}{}
+{Simple ``image'' FITS (as used by CLASS). It must be possible to to
+export multiple spectra simultaneously, using default file name and
+postfix.}
+
+\requirement{}{Removed}{}{}{}{}{}{In a format which can be imported by other popular
+    packages such as Class.
+
+\requirement{Write ASCIIs}{Done1}{}{}{}{}
+{Simple ASCIIs format, suitable for use with programs such as Perl,
+Python, SuperMongo etc.}
+
+\requirement{Header writing}{Started}{1}{0d}{}{}
+{The exported data should retain as much header data as possible. It
+should also be possible to request specific data be written in the
+desired form (B1950 coordinates, optical velocity definition etc).}
+
+\requirement{Import corrections}{Done1}{}{}{}{}
+{The import function should apply relevant corrections (especially
+those which are time dependent) to specific telescopes. See
+$\S$\ref{sec:issues} for a list of currently known issues.}
+
+\requirement{Append output files}{Not started}{1}{3d}{}{} {It must be possible
+to append spectra to existing output files, specifically sdfits and
+asap output files.}
+
+\subsection{Sky subtraction}
+\label{sec:skysubtraction}
+To remove the effects of the passband filter shape and atmospheric
+fluctuations across the band, sky subtraction must be performed on the
+data. The software must be able to do sky subtraction using both
+position switching (quotient spectra) and frequency switching
+techniques.
+
+\requirement{Quotient Spectra}{Done1}{}{}{}{}
+{\label{ref:skysub} Position switched sky subtraction should be
+implemented using the algorithm \medskip\reqeqn{T_{ref} \times
+\frac{S}{R} - T_{sig}} -- removes continuum\bigskip \reqeqn{T_{ref}
+\times \frac{S}{R} - T_{ref}} -- preserves continuum\medskip}
+
+\requirement{Arbitrary reference}{Not started}{2}{}{}{}
+{The user should be able to specify an arbitrarily complex
+reference/source order (which repeats), which can then be used to make
+perform multiple sky subtractions in parallel.}
+
+\requirement{Frequency switching}{Not started}{2}{}{}{}
+{Frequency switched sky subtraction should be supported. (Ref. Liszt,
+1997, A\&AS, 124, 183) }
+
+%\requirement{For wideband multibit sampled data it may be desirable or
+%even required to assume Tsys has a frequency dependency. Appropriate
+%sky subtraction algorithms will need to be investigated.}{3}
+
+\requirement{Pulsar off pulse quotient}{}{}{}{Deferred}{3}
+{For pulsar binned data, the (user specified) off pulse bins can be
+used as the reference spectra. Due to potentially rapid amplitude
+fluctuations, sky subtractions may need to be done on a 
+integration basis.}
+
+Multibeam systems can observe in a nodding fashion (called MX mode at
+Parkes), where the telescope position is nodded between scans so that
+the source is observed in turn by two beams and a reference spectra
+for one beam is obtained while the other is observing the target source.
+
+\requirement{Multibeam MX mode}{Not started}{2}{}{}{}
+{For multibeam systems, it must be possible to perform sky subtraction
+with the source and reference in an alternate pair of beams}
+
+\subsection{Baseline removal}
+
+Baseline removal is needed to correct for imperfections in sky
+subtraction. Depending on the stability of the system, the residual
+spectral baseline errors can be small or quite large. Baseline removal
+is usually done by fitting a function to the (user specified) line
+free channels.
+
+\requirement{Baseline removal}{Done1}{}{}{}{}
+{The software must be able to do baseline removal by fitting a n'th
+order polynomials to the line free channels using a least squares
+method.}
+
+\requirement{Standing wave ripples}{Not started}{3}{}{}{}
+{Removal of standing wave ripples should be done by fitting a Sine
+function to the line free channels.}
+
+\requirement{Robust fitting}{Not started}{3}{}{}{} 
+{``Robust'' fitting functions should be available, which are more
+tolerant to RFI.}
+
+\requirement{Auto-baseline}{Done1}{}{}{}{}
+{Automatic techniques for baselining should be investigated.}
+
+\subsection{Line Profile Fitting}
+
+The user will want to fit multicomponent line profiles to the data in
+a simple manner and be able to manipulate the exact fitting
+parameters.
+
+\requirement{Gaussian fitting}{Done1}{}{}{}{}
+{The software must be able to do multi-component Gaussian fitting of
+the spectra. The initial amplitude, width and velocity of each
+component should be able to be set by the user and specific values to
+be fit should be easily set.}
+
+\requirement{Chi squared}{Done1}{}{}{}{}
+{The reduce Chi squared (or similar statistic) of the fit should given
+to the user, so that they can easily see if adding extra components
+give a statistically significant improvement to the fit.}
+
+%\requirement{The fit parameters should be stored with the data so that
+%the user can work on multiple data sets simultaneously and experiment
+%with different fitting values. These values should be saved to disk
+%along with the data.}{1}
+
+\requirement{Fit multipol data}{}{}{}{Done1}{}
+{For multiple polarisation data, the individual stokes parameters or
+polarisation products should be fit independently.}
+
+\requirement{Export fit parameters}{Not started}{1}{1d}{}{}
+{There should be an easy way of exporting the fit parameter from
+multiple spectra, e.g. as an ASCII table.}
+
+\requirement{Constrained fitting}{Not started}{2}{}{}{}
+{It should be also possible to do constrained fitting of multiple
+hyperfine components, e.g. the NH$_3$ hyperfine components. (The
+constraints may be either the frequency separation of the individual
+components or the amplitude ratio etc.)}
+
+\subrequirement{Specific constrained fitting}{Not started}{1}{4d}{}{}
+{Before arbitrary constrained fitting is available, support for
+specific cases, such as the NH$_3$ hyperfine transition, should e
+added on a case-by-case basis.}
+
+\requirement{Edit fits parameters}{Done1}{}{}{}{}
+{It must be possible to alter the line profile fit parameter values by
+hand at any stage.}
+
+\requirement{Fix fit parameters}{Done1}{}{}{}{}
+{It must be possible to ``fix'' particular values of the line profile
+parameters, so that only subset of lines or (say) the width of a line
+is fit.}
+
+\requirement{Arbitrary line fitting}{Done1}{}{}{}{}
+{The software should allow hooks for line profile shapes other than
+Gaussian to be added in the future, possible user specified.}
+
+\requirement{Save fit parameters}{Done1}{}{}{}{}
+{The fitting parameters for functions which have been fit to the data
+(e.g. for baseline removal or Gaussian fits) should be retained as an
+integral part of the data and stored permanently on disk.}
+
+\requirement{Undo subtracted fits}{Not started}{3}{}{}{}
+{It should be possible to ``undo'' functions which have been
+subtracted from the data (e.g. baseline polynomials).}
+
+\requirement{Gaussian line area}{Not started}{1}{0.2d}{}{} 
+{Optionally the area under a fitted Gaussian should be calculated for
+the user.}
+
+%\makenote{Should it be possible to attach multiple sets of fits to the
+%data (similar to CL tables in classic AIPS), so the user can
+%experiment with different ways of fitting the data?}
+
+%\makenote{Should calculations of rotational temperatures etc be
+%handled when fitting hyperfine components, or should the user be doing
+%this themselves?}
+
+\subsection{Calibration}
+
+The software should handle all basic system temperature (Tsys) and
+gain calibration as well as opacity corrections where relevant. The
+Tsys value should be contained in the rpfits files. The actual
+application of the T$_{\mbox{sys}}$ factor will be applied as part of
+the sky subtraction ($\S$\ref{sec:skysubtraction}). The units of Tsys
+recorded in the data may be either in Jy or Kelvin, which will affect
+how the data is calibrated. The rpfits file does {\em not} distinguish
+if the flux units are Kelvin or Janskys. 
+
+\requirement{Gain-elevation}{Done1}{}{}{}{}
+{Gain elevation corrections should be implemented using a elevation
+dependent polynomial. The polynomial coefficients will be telescope
+and frequency dependent. They will also have a (long term) time
+dependence.}
+
+\requirement{User gain polynomial}{Done1}{}{}{}{}
+{The user may wish to supply their own gain polynomial.}
+
+\requirement{K-Jy conversion}{Done1}{}{}{}{}
+{When required by the user, the spectral units must be converted from
+Kelvin to Jansky. At higher (3mm) frequencies this conversion is often
+not applied. The conversion factor is\medskip \reqeqn{\mbox{Flux (Jy)}
+= \frac{T \times 2 k_b \times 10^{26}}{\eta A}},\medskip\\where $k_b$
+is Boltzmann's constant, A is the illuminated area of the telescope
+and $\eta$ is the efficiency of the telescope (frequency, telescope
+and time dependent)}
+
+\requirement{Scale Tsys}{Done1}{}{}{}{}
+{In some cases the recorded Tsys values will be wrong. There needs to
+be a mechanism to scale the Tsys value and the spectrum if the Tsys
+value has already been applied (i.e. a simple and consistent rescaling
+factor).}
+
+\requirement{Opacity}{Done1}{}{}{}{}
+{The data may need to be corrected for opacity effects, particularly
+at frequencies of 20~GHz and higher. The opacity factor to apply is
+given by\medskip\reqeqn{C_o = e^{\tau/cos(z)}}\medskip\\ where $\tau$
+is the opacity and z is the zenith angle (90-El). These corrections
+will generally be derived from periodic ``skydip'' measurements. These
+values will not be contained in the rpfits files, so there should be a
+simple way of the software obtaining them and interpolating in time
+(the user should not {\em have} to type them in, but may want
+to). Reading in an ASCIIs file which contains the skydip data along
+with a time-stamp would be one possibility.}
+
+\requirement{Tsys variation with freq}{Not started}{3}{}{}{}
+{For wideband, multibit observations, the software should have the
+option to handle Tsys which varies across the band. The exact
+implementation will have to be decided once experience is gained with
+the new Mopra digital filterbank. This will affect the sky subtraction
+algorithms (requirement \reqref{ref:skysub}).}
+
+%\makenote{Is the dependence of gain on frequency weak enough for one
+%set of coefficients for each receiver, or is a full frequency dependent
+%set of values needed?}
+
+%\makenote{Should it be possible to read ``correct'' Tsys values from
+%an external ascii file?} 
+
+\subsection{Editing \& RFI robustness}
+
+In a data set with many observations, individual spectra may be
+corrupted or the data may be affected by RFI and ``birdies''. The user
+needs to be able to easily flag individual spectra or channels. This
+may affect other routines such as sky-subtraction, as this will
+disrupt the reference/source sequence.
+
+\requirement{Spectra flagging}{Started}{1}{1d}{}{}
+{The user must be able to set an entire spectra or part thereof
+(individual polarisation, IF etc) as being invalid.}
+
+\requirement{Channel flagging}{Started}{1}{0.5d}{}{}
+{The user must be able to indicate an individual spectral point or
+range of spectral points are invalid. This should be applied to an
+individual spectra, or set of spectra.}
+
+\subrequirement{Flagged channel plotting}{Done1}{1}{}{}{} {When plotting data
+with flagged spectral channels, the plotting should left a gap (blank)
+in the plotted data|}.
+
+\subrequirement{Flagged channel interpolation}{Not started}{2}{}{}{} 
+{When plotting or processing data (e.g. quotient spectra), the users
+should be able to request that the values for flagged data are
+obtained by interpolation from good data either side of the flagged
+points.}
+
+\requirement{Plot average flux vs time}{Not started}{3}{}{}{}
+{The user should be able to plot the average spectral flux across the
+band, or part of the band, as a function of time and interactively
+select sections of data which should be marked as invalid (similar to
+IBLED in classic aips).}
+
+\requirement{Robust Fitting}{Duplicate}{2}{}{}{}
+{Where relevant, fitting routines etc should have the option of
+selecting RFI tolerant (``robust'') algorithms. This will require
+investigating alternate fitting routines other than the least-squares
+approach.}
+
+\requirement{Birdie finder}{Not started}{2}{}{}{}
+{A routine to automatically find birdies or RFI corrupted data and
+indicate the data as invalid would be useful.}
+
+\requirement{Handle flagged data}{Done1}{}{}{}{}
+{Other routines must be able to cope with portions of data which are
+marked as invalid.}
+
+\subsection{Spectra mathematics and manipulation}
+
+A flexible suite of mathematical operations on the spectra should be
+possible. This should include options such as adding, subtracting,
+averaging and scaling the data. For common operations such as
+averaging and smoothing, it must be simple for the user to invoke the
+function (i.e. not to have to start up a complex spectral
+calculator). Where it makes sense, it should be possible to manipulate
+multiple spectra simultaneously.
+
+The spectral manipulations which should be available are:
+
+\requirement{Add spectra}{Done1}{}{}{}{}{Add or subtract multiple spectra.}
+
+\requirement{Average spectra}{Done1}{1}{}{}{}
+{Averaging multiple spectra, with optional weighting based on Tsys,
+integration or rms.}
+
+\subrequirement{Average spectra with velocity shift}{Not started}{1}{0.2d}{}{}
+{If the velocity of the spectra to be averaged is different, the data
+should be aligned in velocity. The user should be able to turn this
+feature on or off.}
+
+\requirement{Robust averaging}{Not started}{2}{}{}{}
+{Various robust averaging possibilities (e.g. median averaging,
+clipped means etc) should be possible.}
+
+\requirement{Data re-binning}{Done1}{}{}{}{}
+{Re-sampling or re-binning of the data to a lower (or higher) spectral
+resolution (i.e. change the number of spectral points). The
+re-sampling factor may not necessarily be an integer.}
+
+\requirement{Velocity shift}{Done1}{}{}{}{} 
+{It must be possible to shift the data in ``frequency/velocity''. This
+should include channel, frequency and velocity shifts of an arbitrary
+amount.}
+
+\requirement{Spectra smoothing}{Done1}{}{}{}{}
+{Spectral smoothing of the data. Hanning, Tukey, boxcar and Gaussian
+smoothing of variable widths should be possible.}
+
+\requirement{Spectra scaling}{Done1}{}{}{}{}{Scaling of the spectra.}
+
+\requirement{Spectra statistics}{Done1}{}{}{}{}
+{Calculate basic statistical values (maximum, minimum, rms, mean) on a
+range of spectral points. The range may not be contiguous. The
+calculated rms value should be retained with the spectra so it can be
+optionally used for weighted averaging of spectra.}
+
+\requirement{Line flux}{Not started}{2}{}{}{}
+{It must be possible to calculate the flux integral over a range of
+channels. The units should be Jy.km/s (or Kelvin.km/s). The channel
+range for the calculation should be specific via the GUI or CLI.}
+
+\requirement{Line width}{Not started}{2}{}{}{}
+{It must be possible to calculate the numerical ``width'' of a line
+(full width at half maximum type measurement). This should be
+calculated by specifying a channel range and finding the maximum value
+in this range and then finding the interpolated crossing point of the
+data as a user defined fraction of the maximum (default 50\%). The
+profile width and velocity mid-point should then be computed. If the
+profile shape is complex (e.g. double arch) with multiple crossing
+points of the fraction value, the minimum and maximum width values
+should be calculated. There should be the option of using a user
+specified ``maximum value''.}
+
+\requirement{Change rest frequency}{Done1}{}{}{}{}
+{The user must be able to easily change the rest-frequency to which
+the velocity is referenced.}
+
+\requirement{FFT filtering}{Not started}{3}{}{}{}
+{FFT filtering for high- and lowpass filtering and tapering.}
+
+\requirement{FFT to/from autocorrelation function}{Not started}{3}{}{}{}
+{It should be possible to FFT the data to and from power spectra to
+the autocorrelation function.}
+
+\requirement{Cross correlation}{Not started}{3}{}{}{}
+{The user may wish to compute the cross correlation function of two
+spectra. The result should be a standard ``spectra'', which can be
+displayed and analysed using other functions (max, rms etc).}
+
+\requirement{Spectral calculator}{Started}{1}{?}{}{}
+{Complex experiment specific processing can often be done using a
+series of the simple of basic functions. A spectral calculator options
+should be added to the CLI to perform a series of manipulations on a
+set of spectra.}
+
+The user may want to perform specific analysis on the data using the
+functionality above, but wish to do the manipulation between two
+polarisations or IFs. Allowing the functions to also, optionally,
+specify specific polarisations or IF would be an implementation and
+interface nightmare. The simplest solution is to allow the data to be
+``split'' into separate spectra.
+
+\requirement{Slice data}{Not started}{1}{10d}{}{} {It must be possible to
+take multi IF, multibeam or polarisation data and split out the a
+splice of individual spectral portions to form self contained
+spectra.}
+
+\requirement{Slice spectral channels}{Not started}{1}{0d}{}{} {It must be
+possible to select a range of spectral channels to form self contained
+spectra. The channel selection may be different for different IFs.}
+
+\requirement{Merge scantables}{Not started}{1}{5d}{}{}
+{It must be possible to append rows from one scantable onto another}
+
+\subsection{Polarimetry}
+
+The software must fully support polarmetric analysis. This includes
+calibration and basic conversions. Observations may be made with
+linear or circular feed and the backend may or may not compute the
+cross polarisation products. As such the software must cope with a
+variety of conversions. The software should be able to calculate
+stokes parameters with or without solving for leakage terms.
+
+%\makenote{It is debatable whether stokes I is the sum or average or
+%two dual polarisation measurements.}
+
+\requirement{Support polarimetry}{Started}{1}{?}{}{}
+{All functions on the data (calibration, sky subtraction spectral
+mathematics) must support arbitrary, multiple polarisation (linear,
+circular \& stokes and single, dual \& cross polarisations.}
+
+\requirement{Calculate stokes I}{Done1}{}{}{}{}
+{It must be possible to calculate stokes I from single or dual
+polarisation observations.}
+
+\requirement{Average mixed pol data}{Not started}{2}{}{}{}
+{Average a mixture of dual polarisation and single polarisation data
+and form average stokes I (e.g. for a long observation of a source, in
+which one polarisation is missing for some time.}
+
+\requirement{Calculate stokes}{Done1}{}{}{}{}
+{Full stokes parameters should be obtained from dual pol (linear or
+circular) observations where the cross polarisation products have been
+calculated.}
+
+%\requirement{If the observations used linear polarisations and the
+%cross polarisations were not computed, the source needs to be
+%observed with the feeds set at least 3 different parallactic angles
+%(note that if dual linear feeds are available, 2 orthogonal
+%parallactic angles are obtained at once). The Stokes parameters can be
+%solved using a least squares fit to the equation:
+%\reqeqn{Iu/2 + Ip * cos^2 (PA + p)},\\ 
+%where PA is the linear feed position angle, p is the polarisation
+%angle, Iu and Ip and the unpolarised and linearly polarised
+%intensity. {\em Stolen from SPC. I need to write this in more useful
+%language. Is this technique likely to be used anymore?.}}{}{}{}{3}
+
+\requirement{Compute stokes V without crosspol}{Not started}{2}{}{}{}
+{If dual circular polarisation measurements are taken, without
+computing the cross products, the software should still be able to
+compute stokes I and V.}
+
+\requirement{Polarisation leakages}{Not started}{3}{}{}{}
+{The software should be able to calculate leakage terms from a
+calibrator source and correct the data either before or after
+conversion to Stokes. (ref. Johnston, 2002, PASA, 19, 277)}
+
+\requirement{Calibrate position angle}{Not started}{3}{}{}{}
+{The software should be able to determine absolute position angle from
+a calibrator source and correct the data either before or after
+conversion to Stokes.}
+
+\requirement{Zeeman splitting}{Not started}{3}{}{}{}
+{Zeeman splitting factors should be derived from (previous) profile
+fitting and the left and right circular polarisations. The velocity
+shift varies linearly with the magnetic field, but the scaling factor
+depends on the molecule and transition. Scaling factor for common
+transitions should be known by the software and the user able to enter
+factors for less common transitions. Correctly identifying Zeeman
+pairs is crucial in getting the correct result. The software should
+attempt to make an initial guess of pairs (based on component velocity
+and width) but make the user confirm and override the pairing if
+required.}
+
+\subsection{Data Selection}
+While the software is running the user will usually have loaded
+multiple (possibly many) spectra each of which may have multiple IFs,
+data from multiple beams and multiple polarisations. The user will
+want to be able to quickly flip from considering one spectra to
+another and, where relevant, want to perform parallel processing on
+multiple spectra at once (e.g. baselining a sequence of on/off
+observations of the same source which will later be averaged
+together).
+
+\requirement{Spectra selection}{Started}{1}{0d}{}{}
+{The software needs an easy-to-use mechanism to select either
+individual or multiple spectra for viewing, parallel processing
+etc.}
+
+\requirement{Beam/IF selection}{Started}{1}{0d}{}{}
+{An easy-to-use mechanism to select individual IFs, beams or
+polarisations is needed.}
+
+\requirement{Interactive channel selection}{Started}{1}{}{}{}
+{\label{ref:chansel} The range of spectral points to use for baseline
+removal, statistical calculations, RFI editing, analysis etc must be
+easily set by the user from both the CLI and GUI. From the CLI there
+must be the option of setting the range using a variety of units
+(channel number, velocity, frequency). The selection range will
+probably not be a contiguous set of channels, but many sets of
+disjoint channel ranges. For some tasks (such as baseline subtraction
+and statistical values), the channel range should be retained and be
+available as a plot overlay.}
+
+\requirement{Auto-identify reference spectra}{Not started}{1}{5d+}{}{}
+{When performing sky subtraction on many spectra simultaneously, the
+software should have a mechanism for identifying ``on'' and ``off''
+spectra and automatically selecting the signal and quotient
+spectra. The algorithm needs to cope with on/off/on/off sequences as
+well as off/on/on/off. If an individual quotient spectra has been
+marked as invalid, an alternative should be found. User specified
+preference such as ``closest in time'' to ``first reference before
+source'' should be accommodated.}
+
+\requirement{Select source via header values}{Started}{1}{}{}{}
+{The software should be able to select sets of sources based on simple
+regular expression type filtering (wild cards) on a range of header
+values. }
+
+\subrequirement{Select on source name}{Done1}{1}{}{}{}
+{The use should be able to select dana on source name, e.g G309$*$ or
+G309$*$w}
+
+\subrequirement{Select on molecule}{Done1}{2}{}{}{}
+{The use should be able to select data on molecule name, e.g. NH3$*$.}
+
+\subsection{Plugins}
+
+\requirement{Plugins}{Started}{1}{?}{}{}
+{The package should support ``plugins'', user definable
+functions for specific processing. The plugin code must have full
+access (read/write) to the spectra data and headers.}
+
+\requirement{Plugins can reduce dimensions}{Not started}{2}{}{}{}
+{Plugins need to be able to create ``derived'' spectra with reduced
+  dimensions (i.e. less beams, IFs, polarisations or spectral
+  channels)}
+
+\requirement{Simulated data}{Not stated}{3}{}{}{} 
+{The user should be able to create new spectra which the software
+treats the same as the original data. This includes full specification
+of the header items.}
+
+\subsection{Pipelining}
+
+\requirement{Pipelining}{Done1}{}{}{}{}
+{Some sort of pipelining mode is required. This would involve doing a
+quotient spectra, applying appropriate calibration and possibly
+fitting a Gaussian to any lines present.}
+
+\subsection{Methanol Multibeam Survey}
+
+The software may need to support reduction of data from the methanol
+multibeam project. If so the pipelining will need to be flexible and
+powerful enough to support this.
+
+\subsection{Miscellaneous functionality}
+
+\requirement{Position fitting}{Not started}{2}{}{}{}
+{The software should be able to take a simple ``grid'' of observations
+(normally a set of observations in a cross pattern on the sky) and,
+for a subset of channels, fit the position of the emission. The fit
+positions should be either plotted on the screen or exported in a
+simple ASCIIs form.}
+
+\requirement{Kinematic distance}{Not started}{3}{}{}{}
+{The kinematic distance of a source should be calculated using basic
+Galactic rotation models. Multiple Galactic rotation models must be
+supported and a mechanism for easily adding more.}
+
+\requirement{Plot sigma errors on spectra}{Not started}{3}{}{}{}
+{For 1420 MHz observations of HI, the rms (Tsys) values vary
+significantly across the band. The software should be able to compute
+the rms as a function of frequency across the spectra from the
+off-pulse data and then be able to plot n-sigma error bars on the
+spectra.}
+
+\requirement{Simple Mapping}{Not started}{3}{}{}{}
+{It should be possible to take a selection of calibrated spectra which
+are then passed to the ``Gridzilla'' program to produce an image
+cube. Analysis of this cube would be done using external programs
+(e.g. Miriad, aips++)}
+
+\section{Help}
+
+\requirement{Built in help}{Done1}{}{}{}{} 
+{There should be built-in and web-based documentation, which can be
+easily kept up-to-date}
+
+\requirement{Cookbook}{Done1}{}{}{}{}
+{A short and simple end-to-end cookbook for basic data analysis should
+be available.}
+
+\requirement{Programmers Documentation}{Not started}{2}{}{}{}
+{There should be documentation aimed at astronomers wishing to write
+there own scripts, detailing the methods needed and how to get low
+level access to the data.}
+
+\section{Data and meta-data}
+
+\requirement{Handle multi dimensional data}{Done1}{}{}{}{}
+{The software must be capable of handling multi-IF (potentially dozens
+of IFs) and multi-beam data with arbitrary polarisation (e.g. single
+pol, dual pol, full stokes etc).}
+
+\requirement{Handle pulsar data}{Deferred}{}{}{}{}
+{The software should handle pulsar binned data for pulsar absorption
+experiments.}
+
+\subsection{History}
+
+\requirement{History}{Done1}{}{}{}{}
+{A user viewable history of data processing steps should be kept as
+part of the data. Where possible this should be retained when data is
+imported from other packages.}{
+
+\requirement{Convert history to script}{Not started}{2}{}{}{}
+{It should be possible to use the history information to create
+template pipeline scripts for batch processing.}
+
+\subsection{Multiple IFs}
+
+\requirement{Transparently handle multi-IF data}{Done1}{}{}{}{}
+{If multiple IFs are present (currently Tidbinbilla can produce two
+IFs and the new wideband spectrometer for Mopra may have dozens of
+IFs) the software should handle the data transparently. Potentially
+each IF may have a significantly different sky frequency and be
+observing a different molecule or transition with a different rest
+frequency. From the users point of view, simultaneously obtained IFs
+should be kept within the same ``container'' (not split into a myriad
+of separate ``container'').}
+
+\requirement{IFs with different number of spectral channels}{Not started}{1}{}{}{}
+{Separate IFs may have a different number of spectral channels.}
+
+\subsection{Multibeam}
+
+\requirement{Handle multibeam data}{Done1}{}{}{}{}
+{Basic handling of multibeam data should be possible (ie in general
+each beam will be treated as a separate observation, but all within
+the same container). The user should be able to view or process either
+individual beams or all beams in parallel.}
+
+\requirement{Multibeam simultaneous reference/signal}{Not started}{3}{}{}{}
+{The use of a single beam observing a source and the rest of the beams
+as reference beams for sky-subtraction should be investigated.}
+
+\subsection{Robust fitting}
+
+\requirement{Retain raw correlator integrations}{Done1}{}{}{}{}
+{If robust fitting using median filtering is used, then the individual
+integrations from the observations should {\em not} be averaged when
+the data is imported, but retained within a single
+container. Inspection of this data should be optionally of the
+averaged or individual data.}
+
+\subsection{Coordinate frames and units}
+
+\requirement{Flexible coordinate frames}{Done1}{}{}{}{}
+{Coordinate frames and unit selection and handling needs to be
+flexible and relatively transparent to the user (i.e. if the users
+preference is for LSRK velocities, they do not need to worry about the
+reference frame in which the data was observed).}
+
+\requirement{Specific reference frames}{Done1}{}{}{}{}
+{At a minimum the following reference frames and conventions should be
+handled: \setlength{\parindent}{0pt}
+
+\smallskip
+\anitem{Position}{(RA,Dec) in J2000 \& B1950 (as well as other
+  arbitrary epochs), Galactic, (Az,El).}
+
+\anitem{Frequency}{Velocity (Topocentric, Geocentric, Barycentric,
+    Heliocentric, kinematical LSR, dynamical LSR, Rest), Frequency
+    (MHz, GHz), channel number.}
+
+\anitem{Velocity}{ Optical, Radio, Relativistic.}
+
+\anitem{Flux}{ Jansky, Kelvin (mJy etc).}}
+
+\requirement{Data units and frames properly labelled}{Done1}{}{}{}{}
+{All data should be internally labelled with the appropriate
+ coordinate frame and units. If this information is ambiguous for some
+ reason, it should be set when the data is imported and the user
+ should not have to worry about it again.}
+
+\requirement{Current reference frames clear to user}{Done1}{}{}{}{}
+{It should be clear to the user what coordinate frame (velocity,
+position etc) the data is being presented as.}
+
+\requirement{Positional Reference Frame}{Not started}{1}{0.2d}{}{} {The user
+should be able to specify the reference frame (Epoch,Equinox etc) for
+which is used for exporting data, simple mapping output etc. J2000,
+B1950 and Galactic should be supported. The default should be the frame
+in what the data was recorded.}
+
+\requirement{Non-conformist Positional Reference Frame}{Not started}{2}{}{}{} 
+{Non-confirmist positional frames such as Az-El should be supported.}
+
+\subsection{Meta-data}
+
+A comprehensive set of header data should be read from the input data
+files. In general all meta-data available in the rpfits file should be
+retained. The user may wish to enter some specific values by hand.
+
+\requirement{View and edit header data}{Started}{1}{?}{}{}
+{All header data should be viewable and editable by the user. This
+includes changes such as scaling the given Tsys values.}
+
+\requirement{Missing header data}{Done1}{}{}{}{}
+{Missing header data should be handled gracefully, i.e. the software
+should fill the values with ``blanks'' and be able to continue to
+process the data if possible.}
+
+\requirement{User add missing header data}{Not started}{2}{}{}{}
+{The user must be able to add missing header data, which is not
+present in the RPFITs file. It must be possible to add the same header
+data to multiple scans simultaneously.}
+
+\extendedrequirement{Itemised header items}{Started}{1}{}{}{}
+{The following header data would be required per scan:
+\begin{itemize}
+  \item Source name
+  \item Scan type (signal or reference)
+  \item Integration time
+  \item Scan length (actual time of observation, $\ge$ integration time)
+  \item Telescope
+  \item UT time and date of observation
+  \item Telescope elevation of observation
+  \item Parallactic angle
+  \item Beam size
+  \item Scan ID
+  \item Observer
+  \item Project
+  \item Polarisation
+  \item Receiver
+  \item Telescope coordinates
+  \item Weather info (temperature, pressure, humidity)
+  \item User axis display preference (LSR velocity, frequency etc).
+\end{itemize}
+}
+
+\extendedrequirement{IF header items}{Started}{1}{}{}{}
+{\label{req:if}
+The following header data is required for each IF, beam etc:
+\begin{itemize}
+\item Source coordinates and coordinate frame
+\item Frequency/velocity axis definition and type
+\item System Temperature
+\item Beam number
+\item Molecule rest frequency$^\dagger$
+\item Molecular name$^\dagger$
+\item Molecular transition$^\dagger$
+\item Molecular formula$^\dagger$
+\end{itemize}
+}
+
+\requirement{Pretty print formula}{Not started}{3}{}{}{}
+{The molecular formula could be stored with embedded superscripted and
+subscripted symbols for ``pretty'' printing on the plotted, but
+printed in plain text on the CLI or in ASCIIs output}
+
+Some molecular line rest-frequencies are close enough that two or more
+molecules or transitions may be observed in a single IF. Typical
+examples include the 1665/1667~MHz OH maser pair, NH$_3$ transitions,
+and many observations in the 3~mm band.
+\vspace{\parskip}
+
+\requirement{Multiple rest frequencies per IF}{Not started}{2}{}{}{}
+{The software should optionally support multiple lines per IF, by
+storing a set of rest frequencies per IF, rather than a single
+value. The header values in requirement \reqref{req:if} marked with a
+$\dagger$ would all have to be stored as an array of values rather
+than a scalar. A simple mechanism must be possible to change the
+currently ``active'' rest-frequency.}
+
+\section{Installation}
+
+\requirement{Easy installation}{Started}{1}{?}{}{}
+{It must be possible for astronomers to install the software at their
+own institute with either a moderate amount of OS experience or some
+help from the local system administrators. This includes installation
+on a central ``NFS'' server as well as local desktops.}
+
+\requirement{Linux Support}{Started}{1}{3d}{}{}
+{The software must run on major flavours of Linux
+(Fedora/Redhat, Debian, etc).}
+
+\subrequirement{Solaris Support}{Started}{1}{?}{}{}
+{The software must run on Solaris}
+
+\requirement{Run on laptop}{Done1}{}{}{}{}
+{It must be possible for users to install the software on their
+laptops and run with no network connection.}
+
+\requirement{Easy upgrade}{Done1}{}{}{}{}
+{It should be relatively easy to upgrade to the latest version of the
+software.}
+
+\requirement{MacOS/X support}{Not started}{1}{3+d}{}{}
+{The software should run on MacOS/X}
+
+\requirement{Windows support}{Not started}{3}{}{}{}
+{It would be desirable for the software to run on Windows.}
+
+\section{Known Issues}
+\label{sec:issues}
+The following issue are known problems with the data from ATNF
+telescopes, which probably should be automatically corrected for if at
+all possible. The best place to do this is while loading the data.
+
+\subsection{General}
+
+\begin{itemize}
+  \item All polarisations in the RPFITS files are labelled as
+  XX/YY. These need to be relabelled as LL/RR when appropriate.
+\end{itemize}
+
+\subsection{Mopra}
+
+\begin{itemize}
+\item Data obtained in 2002 \& 2003 (and probably before) have an
+  error in the frequency headers (this may be corrected by an external
+  program). \makenote{Nedd Ladd}
+
+\item The (RA,Dec) positions in the data file are in date coordinates
+  not J2000. This causes problems for packages like Class when
+  averaging the data. \makenote{Maria Hunt}
+
+\item It is possible Tsys calibration is inconsistent currently.
+  \makenote{Cormac Purcell??}
+
+\end{itemize}
+
+\subsection{Parkes}
+
+\begin{itemize}
+\item For pulsar data the automatic gain control is disabled. This
+means the nominal Tsys measurement does not change and Tsys per
+integration is encoded in a non-standard way. \makenote{Simon
+Johnston}
+\end{itemize}
+
+\subsection{Tidbinbilla}
+
+\begin{itemize}
+\item All 20-GHz data is calibrated in flux units of Kelvin.
+\item Elevation is not written into the rpfits file.
+\end{itemize}
+
+
+\section{Requirements Matrix}
+
+\begin{longtable}{|l|l|l|c|l|}
+
+\hline
+\bf Requirement & & \bf Status & \bf Priority & \bf Effort \\
+\hline
+\endhead
+\hline
+\endfoot
+\input{reqsum.tex}
+
+\end{longtable}
+
+
+\end{document}
Index: tags/Release2.1.0b/admin/smartref.sty
===================================================================
--- tags/Release2.1.0b/admin/smartref.sty	(revision 1196)
+++ tags/Release2.1.0b/admin/smartref.sty	(revision 1196)
@@ -0,0 +1,207 @@
+\def\fileversion{v1.9}
+\def\filedate{2002/02/28}
+% smartref.sty
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Giuseppe Bilotta
+%
+% Phone +39 095 338103
+%
+% Email: bourbaki@bigfoot.com
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% This package is distributed under the terms
+% of the LaTeX Project Public License
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Please read the readme.txt file for details
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesPackage{smartref}[\filedate\space\fileversion\space Increase
+reference capability (deep reference)]
+%
+\newcounter{@smartlistlen}
+\newcounter{less@smartlist}
+\setcounter{less@smartlist}{-2}
+\newcommand*{\@increasesmartlist}{%
+    \addtocounter{@smartlistlen}{1}%
+    \addtocounter{less@smartlist}{1}}
+\def\sm@rtreflist{{\@currentlabel}}
+\@increasesmartlist
+%
+\newcommand*{\@getsmartreflist}[3]{%
+    \ifx#1%
+        \relax\protect\G@refundefinedtrue%
+        \@latex@warning {SmartReference `#2' on page %
+            \thepage \space undefined}%
+        \nfss@text{\reset@font \bfseries ??}%
+    \else%
+        \expandafter #2#1%
+    \fi}
+\newcounter{@currsmartlistplace}
+\newcommand*{\@getsmartref}[3]{%Paramaters are: #1: Where #2: Label, #3: place in list
+    \edef\@smartlistplace{#3}%
+    \setcounter{@currsmartlistplace}{0}%
+    \edef\@originalsmartlist{%
+        \expandafter\@getsmartreflist\csname sr@#2\endcsname{}{}}%
+    \edef\@currsmartlist{\@originalsmartlist}%
+    \loop%
+        \edef\@currsmartvalue{\expandafter\@car\@currsmartlist\@nil}%
+        \ifnum\the@currsmartlistplace=\theless@smartlist
+            \edef\@currsmartlist{\noexpand{\expandafter\@cdr\@currsmartlist\@nil\noexpand}}%
+        \else
+            \edef\@currsmartlist{\expandafter\@cdr\@currsmartlist\@nil}%
+        \fi
+        \ifnum\the@currsmartlistplace<\@smartlistplace%
+            \addtocounter{@currsmartlistplace}{1}%
+    \repeat
+    \edef#1{\@currsmartvalue}%
+%    \typeout{Got Smart Reference (place #3, value \@currsmartvalue)}%
+    }
+% Hack label: also write the SmartRef data ...
+\def\newsmartlabel{\@newl@bel {sr}}
+%save previous \label
+\AtBeginDocument{%
+    \let\sr@ldlabel=\label
+    \renewcommand*{\label}[1]{%
+        \sr@ldlabel{#1}%
+        \@bsphack%
+        \protected@write\@auxout{}%
+            {\string\newsmartlabel{#1}{\sm@rtreflist}}%
+        \@esphack}%
+% This one is needed for those who use AMS-LaTeX, to ensure that
+% smartref labelling system is used wih equations too (AMS-LaTeX
+% uses its own labeling system for equations
+    \let\ltx@label=\label}
+% Command to add new items to reflist:
+%
+\newtoks\sm@rtintern@ltok
+\newcommand*{\addtoreflist}[1]{% Add item ...
+    \@ifundefined{c@#1}% Check if real counter;
+        {\@nocounterr{#1}}% If not, give error
+        {\@ifundefined{#1posinlist}{% If yes, check if already defined:
+        \sm@rtintern@ltok=\expandafter{\sm@rtreflist}% If not, define everything ...
+        \expandafter\edef\csname sm@rtreflist\endcsname{%
+            \the\sm@rtintern@ltok{\noexpand\csname the#1\endcsname}}% Add it ...
+% Set position of counter in list:
+        \expandafter\edef\csname #1posinlist\endcsname{\the@smartlistlen}%
+        \@increasesmartlist%
+% Define SmartCommands:
+% Define command to get number of <name> for <label>; parameters:
+% #1 is where the value goes, #2 is label name.
+        \typeout{Defining \csname sget#1val\endcsname ...}
+% `smart get <name> value'
+% Usage \sgetchapternum{\holder}{labelname}
+        \expandafter\def\csname sget#1val\endcsname##1##2{%
+            \@ifundefined{sr@##2}{%
+                \G@refundefinedtrue%
+                \@latex@warning{SmartReference `##2' on page \thepage \space undefined}%
+                \def##1{0}}%
+                {\expandafter\@getsmartref{##1}{##2}{\csname #1posinlist\endcsname}}%
+            }%
+% Define command to check if number of <name> for <label> is different from current
+% <name> value; parameters:
+% #1 is label name.
+        \typeout{Defining \csname s#1ref\endcsname ...}
+% `smart <name> \ref'
+% Usage \schapterref{labelname}
+% TODOs:
+%   - check for counters that get reset.
+        \expandafter\newif\csname if#1changed\endcsname % define if consctruct
+        \expandafter\def\csname is#1changed\endcsname##1{% checker for change
+            \csname sget#1val\endcsname{\sm@rtrefvalue}{##1}%
+            \edef\@currentrefvalue{\csname the#1\endcsname}%
+            \ifx\sm@rtrefvalue\@currentrefvalue%
+                \csname #1changedfalse\endcsname%
+            \else%
+                \csname #1changedtrue\endcsname%
+            \fi}%
+        \expandafter\def\csname s#1ref\endcsname##1{%
+            \csname is#1changed\endcsname{##1}%
+            \csname if#1changed\endcsname%
+                , \csname short#1name\endcsname~\csname #1ref\endcsname{##1}%
+%                , \csname short#1name\endcsname~\sm@rtrefvalue%
+            \else%
+                \relax%
+            \fi%
+            }%
+% Define shorthand for \ref{labelname}\s<name>ref{labelname}; parameters:
+% #1 is label name.
+        \typeout{Defining \csname sref#1ref\endcsname ...}
+% Usage \srefchapterref{labelname}
+% If HyperRef is used, let it use no-link refs
+% TODO: define starred and unstarred version; maybe the same for the next.
+        \@ifpackageloaded{hyperref}{%
+            \expandafter\def\csname sref#1ref\endcsname##1{%
+                \@refstar{##1}\csname s#1ref\endcsname{##1}%
+                }
+            }{
+            \expandafter\def\csname sref#1ref\endcsname##1{%
+                \ref{##1}\csname s#1ref\endcsname{##1}%
+                }%
+            }%
+% Define equivalent of \pageref; parameters:
+% #1 is label name.
+        \typeout{Defining \csname #1ref\endcsname ...}
+% Usage \chapterref{labelname}
+        \expandafter\def\csname #1ref\endcsname##1{%
+            \csname sget#1val\endcsname{\sm@rtrefv@lue}{##1}%
+            \sm@rtrefv@lue%
+            }%
+        }{}%
+        }%
+    }
+%
+\DeclareOption{page}{%
+    \AtBeginDocument{%
+    \addtoreflist{page}%
+    \providecommand*\shortpagename{pag.}%
+    }}
+\DeclareOption{chapter}{%
+    \AtBeginDocument{%
+    \addtoreflist{chapter}%
+    \@ifundefined{smartref}{%
+        \newcommand*{\smartref}[1]{%
+            \srefchapterref{#1}%
+            }%
+        }{%
+        \let\nochapter@smartref=\smartref%
+        \renewcommand*{\smartref}[1]{%
+            \nochapter@smartref{#1}\schapterref{#1}}%
+        }
+    %\DeclareRobustCommand{\smartref}%
+    \@ifpackagewith{babel}{italian}{%
+        \providecommand*\shortchaptername{Cap.}}{%
+        \providecommand*\shortchaptername{Chap.}}
+    \typeout{Short chapter name defined: \shortchaptername}%
+    }}
+\DeclareOption{part}{%
+    \AtBeginDocument{%
+    \addtoreflist{part}%
+    \@ifundefined{smartref}{%
+        \newcommand*{\smartref}[1]{%
+            \srefpartref{#1}%
+            }%
+        }{%
+        \let\nopart@smartref=\smartref%
+        \renewcommand*{\smartref}[1]{%
+            \nopart@smartref{#1}\spartref{#1}}%
+        }
+    %\DeclareRobustCommand{\smartref}%
+    \@ifpackagewith{babel}{italian}{%
+        \providecommand*\shortpartname{Parte}}{%
+        \providecommand*\shortpartname{Part}}%
+    \typeout{Short part name defined: \shortpartname}%
+    }}
+\DeclareOption*{%
+    \PackageWarning{smartref}%
+                   {Option `\CurrentOption' unknown!\MessageBreak%
+                    Adding `\CurrentOption' as a reflevel}%
+    \expandafter\addtoreflist\expandafter{\CurrentOption}
+    }
+\DeclareOption{byname}{\AtEndOfPackage{\usepackage{byname}}}
+\ProcessOptions\relax
+\endinput
Index: tags/Release2.1.0b/admin/sourcerelease.sh
===================================================================
--- tags/Release2.1.0b/admin/sourcerelease.sh	(revision 1196)
+++ tags/Release2.1.0b/admin/sourcerelease.sh	(revision 1196)
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+rev=Release12
+version=1.2
+exclude="admin bugs"
+cvsroot=/nfs/atapplic/cvsroot
+
+outdir=asap${version}_src
+echo $outdir
+
+cd /tmp
+cvs -d $cvsroot  export -r $rev -d $outdir asap
+
+for i in ${exclude};
+do
+  rm -rf ${outdir}/$i
+done
+tar jcvf $outdir.tar.bz2 $outdir
+\rm -rf $outdir
+echo "Created source release archive /tmp/${outdir}.tar.bz2"
Index: tags/Release2.1.0b/admin/users.txt
===================================================================
--- tags/Release2.1.0b/admin/users.txt	(revision 1196)
+++ tags/Release2.1.0b/admin/users.txt	(revision 1196)
@@ -0,0 +1,11 @@
+Chris Phillips
+Jim Lovell
+Tony Wong
+Cormac Purcell
+Eric Muller
+Juergen Ott
+Jim Caswell
+Andrea Tarchi + student
+Jill Rathborne
+Maxim Voronkov
+Jacco van Loon
Index: tags/Release2.1.0b/bin/asap
===================================================================
--- tags/Release2.1.0b/bin/asap	(revision 1196)
+++ tags/Release2.1.0b/bin/asap	(revision 1196)
@@ -0,0 +1,51 @@
+#!/bin/sh
+#--------------------------------------------------------------------------
+# Wrapper script for ATNF Spectral Analysis Package (ASAP).
+#---------------------------------------------------------------------------
+# Copyright (C) 2004,2005
+# ATNF
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+#
+# Correspondence concerning this software should be addressed as follows:
+#        Internet email: Malte.Marquarding@csiro.au
+#        Postal address: Malte Marquarding,
+#                        Australia Telescope National Facility,
+#                        P.O. Box 76,
+#                        Epping, NSW, 2121,
+#                        AUSTRALIA
+#
+# $Id$
+#---------------------------------------------------------------------------
+[ -x /usr/bin/clear ] && /usr/bin/clear
+
+echo "Loading ASAP..."
+
+ip="`which ipython`"
+p="`which python`"
+
+# no ipython installation - run without all the goodies
+if [ ! -x "$ip" ]; then
+    echo "Can't find, or no execute permissions for 'ipython'"
+    echo "Running asap through 'python':"
+    $p -c 'from asap import *'
+else
+    # if run for the first time set-up ipythonrc profile and ~/.asap
+    if [ ! -d "${HOME}/.asap" ]; then
+        $p -c "import asap"
+    fi
+    # now execute ipython using the profile
+    $ip -ipythondir "${HOME}/.asap" -p 'asap' $*
+fi
Index: tags/Release2.1.0b/bin/asap_update_data
===================================================================
--- tags/Release2.1.0b/bin/asap_update_data	(revision 1196)
+++ tags/Release2.1.0b/bin/asap_update_data	(revision 1196)
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+import urllib2
+import os
+import tarfile
+
+import shutil
+
+name = 'asap_data.tar.bz2'
+dataurl  = "http://www.atnf.csiro.au/pub/software/asap/data/"
+tmpdata = '/tmp/asap_data.tar.bz2'
+tmpmd5 = tmpdata+'.md5sum'
+
+print "Checking if an update is required."
+md5req = urllib2.Request(dataurl+name+'.md5sum')
+response = urllib2.urlopen(md5req)
+md5file = file(tmpmd5,'w')
+md5file.write(response.read())
+md5file.close()
+md5file = file(tmpmd5)
+md5new = md5file.readlines()[0].split()[0]
+
+# use ASAPDATA if set - allows non-root update
+if os.environ.has_key("ASAPDATA"):
+    asapbase = os.environ["ASAPDATA"]
+else:
+    import asap
+    # get asap module path
+    asapbase = asap.__path__[0]
+try:
+    fl = os.path.join(asapbase, name+".md5sum")
+    data_md5 = file(fl)
+    ls = data_md5.readlines()[0]
+    data_md5.close()
+    md5old = ls.split()[0]
+except IOError:
+    md5old =''
+
+if md5new != md5old:
+    dreq = urllib2.Request(dataurl+name)
+    src = urllib2.urlopen(dreq)
+    print "Update required. Downloading asap data archive...."
+    dst = open(tmpdata, 'w')
+    dst.write(src.read())
+    dst.close()
+    os.chdir(asapbase)
+    tf = tarfile.TarFile.bz2open(tmpdata)
+    print "Extracting data archive in %s.." % asapbase
+    for member in tf.getmembers():
+        tf.extract(member)
+    shutil.copy(tmpmd5, asapbase)
+else:
+    print """Data already at latest available version.
+If you still get errors running asap, please report this."""
Index: tags/Release2.1.0b/bin/install
===================================================================
--- tags/Release2.1.0b/bin/install	(revision 1196)
+++ tags/Release2.1.0b/bin/install	(revision 1196)
@@ -0,0 +1,127 @@
+#!/usr/bin/env python
+
+import distutils.sysconfig as dist
+import shutil
+import os
+import sys
+import getopt
+
+def usage():
+    print """Usage:
+    ./install [-p <prefix for asap binary>]
+              [-m <where to install python module>]
+              [-h] this message
+    """
+
+def get_libs(lib='_asap.so'):
+    lddcommand = "/usr/bin/ldd"
+    lddopts = ""
+    if (sys.platform == "darwin"):
+        lddcommand = "otool"
+        lddopts = "-L"
+    if not os.path.isfile(lddcommand):
+        raise IOError("ldd/otool is not available")
+    if not os.path.exists(lib):
+        raise IOError("File %s not found" %lib)
+    p = os.popen(lddcommand+" "+lddopts+" "+lib)
+    resolved = []
+    unresolved = []
+    for l in p.readlines():
+        lsp = l.split()
+        if lsp[2] == "not":
+            unresolved.append(lsp[0])
+        else:
+            resolved.append(lsp[2])
+    return resolved, unresolved
+
+try:
+    opts, args = getopt.getopt(sys.argv[1:], "p:m:h",
+                               ["exec-prefix=", "module-dir=", "help"])
+
+except getopt.GetoptError:
+    usage()
+    sys.exit(2)
+
+execprefix = dist.EXEC_PREFIX
+moduledir = dist.get_python_lib()
+for k, v in opts:
+    if k in ("-h", "--help"):
+        usage()
+        sys.exit()
+    if k in ("-p", "--exec-prefix"):
+        v = os.path.expanduser(v)
+        v = os.path.expandvars(v)
+        if not os.path.exists(v):
+            print "The specified exec directory %s doesn't exist" % v
+            sys.exit(1)
+        p = os.path.join(v,"bin")
+        if not os.path.exists(p):
+            print "The specified exec directory %s doesn't exist" % p
+            sys.exit(1)
+        execprefix = v
+    if k in ("-m", "--module-dir"):
+        v = os.path.expanduser(v)
+        v = os.path.expandvars(v)
+        if not os.path.exists(v):
+            print "The specified module directory %s doesn't exist" % v
+            sys.exit(1)
+        moduledir = v
+
+print "Looking fo all required libraries..."
+try:
+    libs, missing = get_libs(lib="asap/_asap.so")
+    if len(missing):
+        print " The following libraries are missing:"
+        print missing
+        sys.exit(1)
+    else:
+        print "   All required libraries are present."
+except IOError,msg:
+    print msg+" Skipping test."
+
+print 'Looking for dependent python modules...'
+try:
+    import matplotlib
+except ImportError:
+    print "Matplotlib not found"
+    sys.exit(1)
+if int(matplotlib.__version__.split(".")[1]) < 87:
+    print "Warning: matplotlib version < 0.87." \
+          "This might cause errors. Please upgrade."
+try:
+    import matplotlib.backends.backend_tkagg
+except ImportError:
+    print "Matplotlib doesn't have Tk support"
+    sys.exit(1)
+try:
+    import numpy
+except ImportError:
+    try:
+        import numarray
+    except ImportError:
+        print "You need to have either 'numpy' or 'numarray' installed."
+        sys.exit(1)
+try:
+    import IPython
+except ImportError:
+    print "IPython should be installed."
+    sys.exit(1)
+
+print "   All required modules were found."
+moddir = os.path.join(moduledir, "asap")
+bindir =  os.path.join(execprefix, "bin")
+try:
+    if os.path.exists(moddir):
+        print "Found previous installation of ASAP. Removing..."
+        shutil.rmtree(moddir)
+    print "Installing asap module in %s" % moddir
+    shutil.copytree("asap", moddir)
+    print "Installing asap  scripts in %s" % bindir
+    shutil.copy2("bin/asap", bindir)
+    shutil.copy2("bin/asap_update_data", bindir)
+    if not os.path.exists("asap/data/ephemerides"):
+        print "Warning - no data directory present"
+        print "Please run asap_update_data after the installation is completed."
+    print "Installation completed."
+except OSError, oe:
+    print oe
Index: tags/Release2.1.0b/doc/CHANGELOG
===================================================================
--- tags/Release2.1.0b/doc/CHANGELOG	(revision 1196)
+++ tags/Release2.1.0b/doc/CHANGELOG	(revision 1196)
@@ -0,0 +1,84 @@
+Changelog
+=========
+
+Release Version 2.0 [2006-04-28]
+
+* Major rework of internal data representation. Now supports IFs with
+  different number of spectral channels. This has minimal effect on the
+  user interface.
+* Creation of a "selector" object used for data selection. Allow slicing of
+  data in beam, IF, polarisation as well as selection via header values 
+  (e.g. Tsys). set_cursor is now invalid use set_selection instead.
+* Scantables can be merged
+* scantable.lines() has been removed. Set rest frequencies by value
+  (This will change in 2.1)
+* Multiple data files (e.g. rpf) can be read into a single scantable
+* Polarmetric infrastructure improved to support most observing styles
+  (linear feeds only)
+* ability to convert to/store as Stokes
+* Plotter can now only plot from a single scantable (use merge if necessary)
+* ASCII (text) spectral output changed to be more usable
+* Can change the source position coordinate frame
+* Bug fixes
+* auto_quotient is doing "closest in time reference"
+* option auto-align in velocity when using average_time
+* flagging on a per channel base
+
+
+(Release 1.2.2)
+
+* channel based flagging
+* Added get_elevation, azimuth and parangle
+* Added swap_lineas & invert_phase
+* added allaxes arg to poly_baseline
+* Request: added pager throuput for history and summary
+* Request: Log meesage on auto_quotient()
+* Bug Fix: plotter -set_mode("s","i").
+* Bug fix: plotter -autoscale abcissa limits from all spectra for stacking.
+
+Release Version 1.2.1 [2005-12-06]
+
+* scantable.recalc_azel - recalcualte azimuth and elevation based on
+* pointing Bug fix: plotter - set_layout got overwritten byt plot; row
+  cursor not honoured in _plot_time
+
+
+Release Version 1.2 [2005-11-30]
+
+Code changes
+------------
+* frequency switching: scantable.freq_switch()
+* get a list of sourcenames: scantable.get_sourcename()
+* build quotients from matched _S/_R pairs or 1*_R/n*_S automatically
+* plotting control: plotter.set_colors(), plotter.set_linestyles(),
+  plotter.set_linewidth() all also  via .asaprc parameters
+* non-interactive (non-GUI) plotting
+* ASAP Online (Mopra) Monitor web interface (mini pipeline)
+* user control of multi-panel: plotter.set_layout()
+* histogram plotting: part of linestyles
+* plugins: via ~/.asap/userfuncs.py
+* plotter panel control: rcParams['plotter.ganged']
+* usability: reworked the whole asap startup scripts, now all user adjustable
+  through ~/.asap/
+
+Todos/pending
+--------------
+* documentation update: should be finished by 2005-11-25
+* cookbook
+* release 1.2:  2005-11-30
+
+Major non-targets, resulting from requirements/targets
+-----------------------------------------------------
+* asap logging, which can be turned on and off through .asaprc/rcParams
+ (from asapmon)
+* Data container v2 design study and document ( moved from cycle II )
+
+Not Addressed
+-------------
+
+(deferred to cycle II, as described in support document)
+
+* Flexible data selection
+* "Merging of spectra" (concatenating scantables)
+
+* public defect tracking system
Index: tags/Release2.1.0b/doc/Makefile
===================================================================
--- tags/Release2.1.0b/doc/Makefile	(revision 1196)
+++ tags/Release2.1.0b/doc/Makefile	(revision 1196)
@@ -0,0 +1,34 @@
+TARGET := userguide
+WEBLOC := /tmp
+
+LATEXHTML := /usr/bin/latex2html
+LATEXHTMLOPTS := -local_icons
+DVIPDF := /usr/bin/dvipdf
+LATEX := /usr/bin/latex
+
+all: pdf html
+
+$(TARGET): pdf html
+
+latex:
+	$(LATEX) $(TARGET)
+	makeindex $(TARGET)
+	$(LATEX) $(TARGET)
+
+pdf: latex
+	$(DVIPDF) $(TARGET)
+
+html:
+	$(LATEXHTML) $(LATEXHTMLOPTS) $(TARGET)
+
+clean:
+	@rm -f $(TARGET).log $(TARGET).aux $(TARGET).dvi
+
+realclean: clean
+	@rm -f $(TARGET).pdf $(TARGET)
+
+install:
+	-cp -rf  $(TARGET) $(WEBLOC)
+	-cp -f $(TARGET).pdf $(WEBLOC)
+
+.PHONY: install clean realclean
Index: tags/Release2.1.0b/doc/Plotting_Requirements.txt
===================================================================
--- tags/Release2.1.0b/doc/Plotting_Requirements.txt	(revision 1196)
+++ tags/Release2.1.0b/doc/Plotting_Requirements.txt	(revision 1196)
@@ -0,0 +1,37 @@
+ASAP Plotting Requirements
+
+* should be based on matplotlib (installed in
+  /nfs/DEBIANlocal - > /usr/local on linux)
+  
+* should use the Tk frontend as others are not supported
+  on our systems (yet)
+  
+* be in interactive mode, with building of draw lists, so that it only
+  refreshes once, at the end. This needs some tweaking of the code. 
+  (example attached - example_delayed_plot.py)
+  
+* should take (x,y, mask) and transparently handle the mask. Masked data should be either blank or dashed
+
+* should take units/labels for axis labelling, and title string
+
+* should (auto)plot legends when overplotting
+
+* should auto-increment colours when overplotting
+
+* autoscaling when overplotting
+
+* should provide panel plotting e.g, cols=Polarisations, rows=integrations
+
+* should have position tracking (example attached - example_position_tracking.py)
+
+* should allow region selection with cursor for both zooming and masking
+
+* should expose general matplotlib functions for access at lower-level
+
+* should have an OO style interface
+
+Note:
+* matplotlib has to interfaces - a functional "matlab" interface and classes. 
+* matplotlib currently only works using the "TkAgg" interface, this will be until a Debian stable update is released.
+"matplotlib.use('TkAgg')" should be on top of the plotting class.
+* zooming has to be hardcoded using Tk events and changed later when other widgetsets will be available.
Index: tags/Release2.1.0b/doc/README
===================================================================
--- tags/Release2.1.0b/doc/README	(revision 1196)
+++ tags/Release2.1.0b/doc/README	(revision 1196)
@@ -0,0 +1,38 @@
+This is a binary distribution of asap.
+
+To be able to run the following additional packages need to be installed.
+
+* gcc/g++
+* python
+* boost_python
+* cfitsio
+* lapack
+* blas
+* g2c
+* matplotlib
+* numpy or numarray
+* ipython
+
+INSTALL
+=======
+
+To install simply run (as root or 'sudo')
+
+./install
+
+which will install asap into the default location.
+To do a customised install run
+
+./install -h
+
+for help.
+
+
+RUN
+===
+
+simply type
+
+asap
+
+at the prompt (if you use tcsh you might need to run 'rehash' first)
Index: tags/Release2.1.0b/doc/release-checklist.txt
===================================================================
--- tags/Release2.1.0b/doc/release-checklist.txt	(revision 1196)
+++ tags/Release2.1.0b/doc/release-checklist.txt	(revision 1196)
@@ -0,0 +1,29 @@
+Release 1.0 Notes
+=================
+
+1) Build asap
+   - ssh zorro ( any debian linux machine with nfs write permissions )
+   - make && make install
+   - updated /nfs/sources/INSTALL.log
+
+2) installed docs by hand - no make targets yet
+   - cd doc
+   - ln -s cookbook.tex userguide.tex
+   - latex userguide
+   - makeindex userguide.idx
+   - latex userguide
+   - latex2html -local_icons userguide.tex
+   - dvipdf userguide.dvi
+   - cp -r userguide /nfs/wwwatdocs/computing/software/asap
+   - manually edit index.html and add userguide
+   - add refman doc
+   - cd /usr/local/lib/python2.3/site-packages
+   - epydoc --html --noframes --inheritance listed -u http://www.atnf.csiro.au/computing/software/asap -o /nfs/wwwatdocs/computing/software/asap/refman asap
+   - epydoc --pdf -o /tmp/refman_pdf asap
+   - cp /tmp/refman_pd/api.pdf /nfs/wwwatdocs/computing/software/asap/refman.pdf
+
+3) cvs branching for release
+   - cvs -R tag ReleaseNNN
+   - cvs rtag -r ReleaseNNN -b ReleaseNNN-fixes asap
+
+4) Check that asap is rdisted (/nfs/DEBIANlocal) to the sites
Index: tags/Release2.1.0b/doc/userguide.tex
===================================================================
--- tags/Release2.1.0b/doc/userguide.tex	(revision 1196)
+++ tags/Release2.1.0b/doc/userguide.tex	(revision 1196)
@@ -0,0 +1,1864 @@
+\documentclass[11pt]{article}
+\usepackage{a4}
+\usepackage{calc}
+\usepackage[dvips]{graphicx}
+\usepackage{makeidx}
+
+% Adjust the page size
+\addtolength{\oddsidemargin}{-0.4in}
+\addtolength{\evensidemargin}{+0.4in}
+\addtolength{\textwidth}{+0.8in}
+
+\setlength{\parindent}{0mm}
+\setlength{\parskip}{1ex}
+
+\title{ATNF Spectral Analysis Package\\User Guide v2.0 }
+\author{Chris Phillips}
+
+\newcommand{\cmd}[1]{{\tt #1}}
+
+\newcommand{\asaprc}[3]{
+  \begin{minipage}[t]{45mm}#1\end{minipage}
+  \begin{minipage}[t]{30mm}\raggedright #2\end{minipage}\hspace{3mm}
+  \begin{minipage}[t]{\textwidth-75mm}#3\end{minipage}
+}
+
+\makeindex
+
+
+\begin{document}
+
+\maketitle
+
+\section{Introduction}
+
+ASAP is a single dish spectral line processing package currently being
+developed by the ATNF. It is intended to process data from all ATNF
+antennas, and can probably be used for other antennas if they can
+produce ``Single Dish FITS'' format. It is based on the AIPS++
+package.
+
+This userguide has been updated for the ASAP 2.0 release which has a
+number of significant changes which affect the user interface. Please
+report any mistakes you find.
+
+\section{Installation and Running}
+
+Currently there are installations running on Linux machines at
+
+\begin{itemize}
+\item Epping - use hosts {\tt draco} or {\tt hydra}
+\item Narrabri - use host {\tt kaputar}
+\item Parkes - use host {\tt bourbon}
+\item Mopra - use host {\tt minos}
+\end{itemize}
+
+\index{Running}To start asap log onto one of these Linux hosts and enter
+
+\begin{verbatim}
+  > cd /my/data/directory
+  > asap
+\end{verbatim}
+
+This starts the ASAP. To quit, you need to type \verb+^+-d
+(control-d) or type \cmd{\%Exit}.
+
+\section{Interface}
+
+\index{Interface}ASAP is written in C++ and python. The user interface
+uses the ``ipython'' interactive shell, which is a simple interactive
+interface to python. The user does not need to understand python to
+use this, but certain aspects python affect what the user can do.  The
+current interface is object oriented.
+
+\subsection {Integer Indices are 0-relative}
+
+Please note, all integer indices in ASAP and iPython are {\bf 0-relative}.
+
+\subsection{Objects}
+\index{objects}
+The ASAP interface is based around a number of ``objects'' which the
+user deals with. Objects range from the data which have been read from
+disk, to tools used for fitting functions to the data. The following
+main objects are used :
+
+\begin{itemize}
+  \item[\cmd{scantable}] The data container (actual spectra and header
+    information)
+  \item[\cmd{selector}] Allows the user to select a subsection of the
+    data, such as a specified or range of beam numbers, IFs, etc.
+  \item[\cmd{plotter}] A tool used to plot the spectral line data
+  \item[\cmd{fitter}] A tool used to fit functions to the spectral data
+  \item[\cmd{reader}] A tool which can be used to read data from disks
+    into a scantable object (advanced use).
+\end{itemize}
+
+There can be many objects of the same type. Each object is referred to
+by a variable name made by the user. The name of this variable is not
+important and can be set to whatever the user prefers (i.e. ``s'' and
+``ParkesHOH-20052002'' are equivalent).  However, having a simple and
+consistent naming convention will help you a lot.
+
+\subsection{Member Functions (functions)}
+
+\index{Functions!member}Following the object oriented approach,
+objects have associated ``member functions'' which can either be used
+to modify the data in some way or change global properties of the
+object. In this document member functions will be referred to simply
+as functions. From the command line, the user can execute these
+functions using the syntax:
+\begin{verbatim}
+  ASAP> out = object.function(arguments)
+\end{verbatim}
+
+Where \cmd{out} is the name of the returned variable (could be a new
+scantable object, or a vector of data, or a status return),
+\cmd{object} is the object variable name (set by the user),
+\cmd{function} is the name of the member function and \cmd{arguments}
+is a list of arguments to the function. The arguments can be provided
+either though position or \cmd{name=}.  A mix of the two can be used.
+E.g.
+
+\begin{verbatim}
+  ASAP> av = scans.average_time(msk,weight='tsys')
+  ASAP> av = scans.average_time(mask=msk,weight='tsys')
+  ASAP> av = scans.average_time(msk,tsys)
+  ASAP> scans.poly_baseline(mask=msk, order=0, insitu=True)
+  ASAP> scans.poly_baseline(msk,0,True)
+  ASAP> scans.poly_baseline(mask, insitu=True)
+\end{verbatim}
+
+\subsection{Global Functions}
+
+\index{Functions!global}It does not make sense to implement some functions as member
+functions, typically functions which operate on more than one
+scantable (e.g. time averaging of many scans). These functions will
+always be referred to as global functions.
+
+\subsection{Interactive environment}
+
+\index{ipython!environment}ipython has a number of useful interactive
+features and a few things to be aware of for the new user.
+
+\subsubsection{String completion}
+
+\index{ipython!string completion}Tab completion is enabled for all
+function names. If you type the first few letters of a function name,
+then type {\tt <TAB>} the function name will be auto completed if it
+is un-ambiguous, or a list of possibilities will be
+given. Auto-completion works for the user object names as well as
+function names. It does not work for filenames, nor for function
+arguments.
+
+Example
+\begin{verbatim}
+  ASAP> scans = scantable('MyData.rpf')
+  ASAP> scans.se<TAB>
+  ASAP> scans.set_in<TAB>
+scans.set_cursor      scans.set_freqframe   scans.set_selection
+scans.set_doppler     scans.set_instrument  scans.set_unit
+scans.set_fluxunit    scans.set_restfreqs
+
+  ASAP> scans.set_instrument()
+\end{verbatim}
+
+\subsubsection{Leading Spaces}
+
+\index{ipython!leading space}Python uses leading space to mark blocks
+of code. This means that it you start a command line with a space, the
+command generally will fail with an syntax error.
+
+\subsubsection{Variable Names}
+
+\index{ipython!variable names}During normal data processing, the user
+will have to create named variables to hold spectra etc. These must
+conform to the normal python syntax, specifically they cannot contain
+``special'' characters such as \@ \$ etc and cannot start with a
+number (but can contain numbers).  Variable (and function) names are
+case sensitive.
+
+\subsubsection{Unix Interaction}
+
+\index{ipython!unix interaction}Basic unix shell commands (\cmd{pwd},
+\cmd{ls}, \cmd{cd} etc) can be issued from within ASAP. This allows
+the user to do things like look at files in the current directory. The
+shell command ``\cmd{cd}'' works within ASAP, allowing the user to
+change between data directories. Unix programs cannot be run this way,
+but the shell escape ``$!$'' can be used to run arbitrary
+programs. E.g.
+
+\begin{verbatim}
+  ASAP> pwd
+  ASAP> ls
+  ASAP> cd /my/data/directory
+  ASAP> ! mozilla&
+\end{verbatim}
+
+\subsection{Help}
+
+\index{Help}ASAP has built in help for all functions. To get a list of
+functions type:
+
+\begin{verbatim}
+  ASAP> commands()
+\end{verbatim}
+
+To get help on specific functions, the built in help needs to be given
+the object and function name. E.g.
+
+\begin{verbatim}
+  ASAP> help scantable.get_scan # or help(scantable.get_scan)
+  ASAP> help scantable.stats
+  ASAP> help plotter.plot
+  ASAP> help fitter.plot
+
+  ASAP> scans = scantable('mydata.asap')
+  ASAP> help scans.get_scan # Same as above
+\end{verbatim}
+
+Global functions just need their name
+
+\begin{verbatim}
+  ASAP> help average_time
+\end{verbatim}
+
+Note that if you just type \cmd{help} the internal ipython help is
+invoked, which is probably {\em not} what you want. Type \verb+^+-d
+(control-d) to escape from this.
+
+\subsection{Customisation - .asaprc}
+
+\index{.asaprc}ASAP use an \cmd{.asaprc} file to control the user's
+preference of default values for various functions arguments. This
+includes the defaults for arguments such as \cmd{insitu}, scantable
+\cmd{freqframe} and the plotters \cmd{set\_mode} values. The help on
+individual functions says which arguments can be set default values
+from the \cmd{.asaprc} file. To get a sample contents for the
+\cmd{.asaprc} file use the command \cmd{list\_rcparameters}.
+
+Common values include:
+\begin{verbatim}
+  # apply operations on the input scantable or return new one
+  insitu                     : False
+
+  # default output format when saving scantable
+  scantable.save             : ASAP
+
+  # default frequency frame to set when function
+  # scantable.set_freqframe is called
+  scantable.freqframe        : LSRK
+
+  # auto averaging on read
+  scantable.autoaverage      : True
+\end{verbatim}
+
+For a complete list of \cmd{.asaprc} values, see the Appendix.
+
+\section{Scantables}
+\index{Scantables}
+\subsection {Description}
+
+\subsubsection {Basic Structure}
+
+\index{Scantable!structure}ASAP data handling works on objects called
+scantables.  A scantable holds your data, and also provides functions
+to operate upon it.
+
+The building block of a scantable is an integration, which is a single
+row of a scantable. Each row contains just one spectrum for each beam, IF and
+polarisation. For example Parkes multibeam data would contain many
+beams, one IF and 2-4 polarisations, while the new Mopra 8-GHz
+filterbank will eventually produce one beam, many IFs, and 2-4
+polarisations. All of the combinations of Beams/IFs an Polarisations are
+contained in seperate rows. These rows are grouped in cycles (same time stamp).
+
+A collection of cycles for one source is termed
+a scan (and each scan has a unique numeric identifier, the SCANNO). A
+scantable is then a collection  of one or more scans. If you have
+scan-averaged your data in time, i.e. you have averegaed all cycles within
+a scan, then each scan would hold just one (averaged) integration.
+
+Many of the functions which work on scantables can either return a
+new scantable with modified data or change the scantable insitu. Which
+method is used depends on the users preference. The default can be
+changed via the {\tt .asaprc} resource file.
+
+For example a Mopra scan with a  4s intergration time, two IFs and
+dual polarisations has two (2s) cycles.
+\begin{verbatim}
+    SCANNO  CYCLENO BEAMNO IFNO POLNO
+    0       0       0      0    0
+    0       0       0      0    1
+    0       0       0      1    0
+    0       0       0      1    1
+    0       1       0      0    0
+    0       1       0      0    1
+    0       1       0      1    0
+    0       1       0      1    1
+\end{verbatim}
+
+
+\subsubsection {Contents}
+
+\index{Scantable!contents}A scantable has header information and data
+(a scantable is actually an AIPS++ Table and it is generally stored in
+memory when you are manipulating it with ASAP.  You can save it to
+disk and then browse it with the AIPS++ Table browser if you know how
+to do that !).
+
+The data are stored in columns (the length of a column is the number of
+rows/spectra of course).
+
+
+Two important columns are those that describe the frequency setup.  We mention
+them explicitly here because you need to be able to understand the presentation
+of the frequency information and possibly how to manipulate it.
+
+These columns are called FREQ\_ID and MOLECULE\_ID.  They contain indices, for
+each IF, pointing into tables with all of the frequency and rest-frequency
+information for that integration. 
+
+There are of course many other columns which contain the actual spectra,
+the flags, the Tsys, the source names and so on.
+
+There is also a function \cmd{summary} to list a summary of the scantable.
+You will find this very useful.
+
+Example:
+
+\begin{verbatim}
+  ASAP> scans = scantable('MyData.rpf')
+  ASAP> scans.summary()                # Brief listing
+
+  # Equivalent to brief summary function call
+  ASAP> print scan
+\end{verbatim}
+
+The summary function gives you a scan-based summary, presenting the
+scantable as a cascading view of Beams and IFs. Note that the output
+of summary is redirected into your current pager specified by the
+\$PAGER environment variable. If you find the screen is reset to the
+original state when summary is finished (ie the output from summary
+disappears), you may need to set the \$LESS environment variable to
+include the \cmd{-X} option.
+
+\subsection{Data Selection}
+\label{sec:selection}
+
+ASAP contains flexible data selection. Data can be selected based on
+IF, beam, polarisation, scan number as well as values such as
+Tsys. Advanced users can also make use of the AIPS++ TAQL language to
+create selections based on almost any of the values recorded.
+
+Selection is based on a \cmd{selector} object. This object is created
+and various selection functions applied to it (\cmd{set\_ifs},
+\cmd{set\_beams} etc). The selection object then must be applied to a
+scantable using the \cmd{set\_selection} function. A single selection
+object can be created and setup then applied to multiple scantables.
+
+Once a selection has been applied, all following functions will only
+``see'' the selected spectra (including functions such as
+\cmd{summary}). The selection can then be reset and all spectra are
+visible. Note that if functions such as \cmd{copy} are run on a
+scantable with active selection, only the selected spectra are copied.
+
+The common selection functions are:
+
+\begin{itemize}
+
+\item[\cmd{set\_beams}] Select beams by index number
+\item[\cmd{set\_ifs}] Select ifs by index number
+\item[\cmd{set\_name}] Select by source name. Can contain ``*'' as a
+wildcard, e.g. ``Orion*\_R''.
+\item[\cmd{set\_ifs}] Select IFs by index number
+\item[\cmd{set\_polarisation}] Select by polarisation index or
+name. If polarisation names are given, the data will be on-the-fly
+converted (for example from linears to Stokes).
+\item[\cmd{set\_query}] Set query directly. For power users only!
+\item[\cmd{set\_tsys}] Select data based on Tsys. Also example of user
+definable query.
+\item[\cmd{reset}] Reset the selection to include all spectra.
+
+
+Note that all indices are zero based.
+
+Examples:
+
+\begin{verbatim}
+  ASAP> selection = selector()         # Create selection object
+  ASAP> selection.set_ifs(0)           # Just select the first IF
+  ASAP> scans.set_selection(selection) # Apply the selection
+  ASAP> print scans                    # Will just show the first IF
+
+  ASAP> selection.set_ifs([0,1])       # Select the first two IFs
+  ASAP> selection.set_beams([1,3,5])   # Also select three of the beams
+  ASAP> scans.set_selection(selection) # Apply the selection
+
+  ASAP> selection.set_name('G308*')     # Select by source name
+
+  ASAP> selection.reset()              # Turn off selection
+  ASAP> scans.set_selection(selection) # Apply the reset selection
+
+\end{verbatim}
+
+\end{itemize}
+
+\subsection{State}
+
+\index{Scantable!state}Each scantable contains "state"; these are
+properties applying to all of the data in the scantable.
+
+Examples are the selection of beam, IF and polarisation,  spectral unit
+(e.g. km/s), frequency reference frame (e.g. BARY) and velocity Doppler
+type (e.g. RADIO).
+
+\subsubsection{Units, Doppler and Frequency Reference Frame}
+
+The information describing the frequency setup for each integration
+is stored fundamentally in frequency in the reference frame
+of observation (E.g. TOPO).
+
+When required, this is converted to the desired reference frame
+(e.g. LSRK), Doppler (e.g. OPTICAL) and unit (e.g. km/s) on-the-fly.
+This is important, for example, when you are displaying the data or
+fitting to it. The reference frame is set on file read to the value
+set in the user \cmd{.asaprc} file.
+
+For units, the user has the choice of frequency, velocity or channel.
+The \cmd{set\_unit} function is used to set the current unit for a
+scantable. All functions will (where relevant) work with the selected
+unit until this changes. This is mainly important for fitting (the fits
+can be computed in any of these units), plotting and mask creation.
+
+The velocity definition can be changed with the \cmd{set\_doppler}
+function, and the frequency reference frame can be changed with the
+\cmd{set\_freqframe} function.
+
+Example usage:
+
+\begin{verbatim}
+  ASAP> scans = scantable('2004-11-23_1841-P484.rpf') # Read in the data
+  ASAP> scans.set_freqframe('LSRK')  # Use the LSR velocity frame
+  ASAP> scans.set_unit('km/s')        # Use velocity for plots etc from now on
+  ASAP> scans.set_doppler('OPTICAL')  # Use the optical velocity convention
+  ASAP> scans.set_unit('MHz')         # Use frequency in MHz from now on
+\end{verbatim}
+
+
+\subsubsection{Rest Frequency}
+
+\index{Scantable!rest frequency}ASAP reads the line rest frequency
+from the RPFITS file when reading the data. The values stored in the
+RPFITS file are not always correct and so there is a function
+\cmd{set\_restfreq} to set the rest frequencies for the currently
+selected data.
+
+For each integration, there is a rest-frequency per IF (the rest
+frequencies are just stored as a list with an index into them).
+There are a few ways to set the rest frequencies with this function.
+
+If you specify just one rest frequency, then it is set for all IF.
+
+\begin{verbatim}
+  # Set all IFs
+  ASAP> scans.set_restfreqs(freqs=1.667359e9)
+\end{verbatim}
+
+If set a rest frequency for each IF, specify a list of frequencies (of
+length the number of IFs).  Regardless of the source, the rest
+frequency will be set for each IF to the corresponding value in the
+provided list.
+
+\begin{verbatim}
+  # Set rest frequency for all IFs
+  ASAP> scans.set_restfreqs(freqs=[1.6654018e9,1.667359e9,])
+
+\end{verbatim}
+
+{\em Currently the following is not implemented}
+
+In both of the above modes, you can also specify the rest frequencies via
+names in a known list rather than by their values.
+
+Examples:
+
+\begin{verbatim}
+  ASAP> scans.set_restfreqs(freqs=['OH1665','OH1667'])
+\end{verbatim}
+
+
+\subsubsection{Masks}
+
+\index{Masks}\index{Scantable!masks}
+
+Many tasks (fitting, baseline subtraction, statistics etc) should only
+be run on range of channels. Depending on the current ``unit'' setting
+this range is set directly as channels, velocity or frequency
+ranges. Internally these are converted into a simple boolean mask for
+each channel of the abscissa. This means that if the unit setting is
+later changed, previously created mask are still valid. (This is not
+true for functions which change the shape or shift the frequency
+axis).  You create masks with the function \cmd{create\_mask} and this
+specified the channels to be included in the selection. When setting
+the mask in velocity, the conversion from velocity to channels is
+based on the current selection, specified row and selected frequency
+reference frame.
+
+
+Note that for multi IF data with different number of channels per IF a
+single mask cannot be applied to different IFs. To use a mask on such
+data the selector should be applied to select all IFs with the same
+number of channels.
+
+Example :
+\begin{verbatim}
+
+  # Select channel range for baselining
+  ASAP> scans.set_unit('channels')
+  ASAP> msk = scans.create_mask([100,400],[600,800])
+
+  # Select velocity range for fitting
+  ASAP> scans.set_unit('km/s')
+  ASAP> msk = scans.create_mask([-30,-10])
+\end{verbatim}
+
+Sometimes it is more convenient to specify the channels to be
+excluded, rather included.  You can do this with the ``invert''
+argument.
+
+Example :
+\begin{verbatim}
+  ASAP> scans.set_unit('channels')
+  ASAP> msk = scans.create_mask([0,100],[900-1023], invert=True)
+\end{verbatim}
+
+By default \cmd{create\_mask} uses the frequency setup of the first row
+to convert velocities into a channel mask. If the rows in the data
+cover different velocity ranges, the scantable row to use should be
+specified:
+
+\begin{verbatim}
+  ASAP> scans.set_unit('km/s')
+  ASAP> msk = q.create_mask([-30,-10], row=5)
+\end{verbatim}
+
+Because the mask is stored in a simple python variable, the users is
+able to combine masks using simple arithmetic. To create a mask
+excluding the edge channels, a strong maser feature and a birdie in
+the middle of the band:
+
+\begin{verbatim}
+  ASAP> scans.set_unit('channels')
+  ASAP> msk1 = q.create_mask([0,100],[511,511],[900,1023],invert=True)
+  ASAP> scans.set_unit('km/s')
+  ASAP> msk2 = q.create_mask([-20,-10],invert=True)
+
+  ASAP> mask = msk1 and msk2
+\end{verbatim}
+
+
+\subsection{Management}
+
+\index{Scantable!management}During processing it is possible to create
+a large number of scan tables. These all consume memory, so it is best
+to periodically remove unneeded scan tables. Use \cmd{list\_scans} to
+print a list of all scantables and \cmd{del} to remove unneeded ones.
+
+Example:
+
+\begin{verbatim}
+  ASAP> list_scans()
+  The user created scantables are:
+  ['s', 'scans', 'av', 's2', 'ss']
+
+  ASAP> del s2
+  ASAP> del ss
+\end{verbatim}
+
+\section{Data Input}
+
+\index{Reading data}Data can be loaded in one of two ways; using the
+reader object or via the scantable constructor. The scantable method
+is simpler but the reader allows the user more control on what is read.
+
+\subsection{Scantable constructor}
+
+\index{Scantable constructor}\index{Scantable!constructor}This loads
+all of the data from filename into the scantable object scans and
+averages all the data within a scan (i.e.  the resulting scantable
+will have one row per scan).  The recognised input file formats are
+RPFITS, SDFITS (singledish fits), ASAP's scantable format and aips++
+MeasurementSet2 format.
+
+Example usage:
+
+\begin{verbatim}
+  ASAP> scan = scantable('2004-11-23_1841-P484.rpf')
+
+  # Don't scan average the data
+  ASAP> scan = scantable('2004-11-23_1841-P484.rpf', average=False)
+\end{verbatim}
+
+
+\subsection{Reader object}
+
+\index{Reader object}\index{Scantable!reader object}For more control
+when reading data into ASAP, the reader object should be used.  This
+has the option of only reading in a range of integrations, only a
+specified beam or IF and does not perform any scan averaging of the
+data, allowing analysis of the individual integrations.  Note that due
+to limitation of the RPFITS library, only one reader object can be
+open at one time reading RPFITS files.  To read multiple RPFITS files,
+the old reader must be destroyed before the new file is opened.
+However, multiple readers can be created and attached to SDFITS files.
+
+
+Example usage:
+
+\begin{verbatim}
+  ASAP> r = reader('2003-03-16_082048_t0002.rpf')
+  ASAP> r.summary()
+  ASAP> scan = r.read()
+  ASAP> del r
+\end{verbatim}
+
+\section{Basic Processing}
+
+In the following section, a simple data reduction to form a quotient
+spectrum of a single source is followed.  It has been assume that the
+\cmd{.asaprc} file has {\em not} been used to change the \cmd{insitu}
+default value from \cmd{True}.
+
+\subsection{Auto quotient}
+\index{Auto quotient}Quotients can be computed ``automatically''. This
+requires the data to have matching source/reference pairs or one
+reference for multiple sources. Auto quotient assumes reference scans
+have a trailing ``\_R'' in the source name for data from Parkes and
+Mopra, and a trailing ``e'' or ``w'' for data fro, Tidbinbilla.
+
+\begin{verbatim}
+  ASAP> q = s.auto_quotient()
+\end{verbatim}
+
+By default the quotient spectra is calculated
+to preserve continuum emission. If you wish to remove the continuum
+contribution, use the \cmd{preserve} argument:
+
+\begin{verbatim}
+ ASAP> q = s.auto_quotient(preserve=True)
+\end{verbatim}
+
+%If this is not sufficient the following alternative method can be used.
+%
+%\subsection{Separate reference and source observations}
+%
+%\index{Quotient spectra}Most data from ATNF observatories
+%distinguishes on and off source data using the file name. This makes
+%it easy to create two scantables with the source and reference
+%data. As long as there was exactly one reference observation for each
+%on source observation for following method will work.
+%
+%For Mopra and Parkes data:
+%\begin{verbatim}
+%  ASAP> r = scans.get_scan('*_R')
+%  ASAP> s = scans.get_scan('*_S')
+%\end{verbatim}
+%
+%For Tidbinbilla data
+%\begin{verbatim}
+%  ASAP> r = scans.get_scan('*_[ew]')
+%  ASAP> s = scans.get_scan('*_[^ew]')
+%\end{verbatim}
+%
+%\subsection{Make the quotient spectra}
+%
+%Use the quotient function
+%
+%\begin{verbatim}
+%  ASAP> q = s.quotient(r)
+%\end{verbatim}
+%
+%This uses the rows in scantable \cmd{r} as reference spectra for the
+%rows in scantable \cmd{s}. Scantable \cmd{r} must have either 1 row
+%(which is applied to all rows in \cmd{s}) or both scantables must have
+%the same number of rows.
+
+\subsection{Time average separate scans}
+
+\index{Time average}If you have observed the source with multiple
+source/reference cycles you will want to scan-average the quotient
+spectra together.
+
+\begin{verbatim}
+ ASAP> av = q.average_time()
+\end{verbatim}
+
+If for some you want to average multiple sets of scantables together
+you can:
+
+\begin{verbatim}
+ ASAP> av = average_time(q1, q2, q3)
+\end{verbatim}
+
+The default is to use integration time weighting. The alternative is
+to use none, variance, Tsys weighting or Tsys \& integration time.
+
+\begin{verbatim}
+ ASAP> av = average_time(q, weight='tintsys')
+\end{verbatim}
+
+To use variance based weighting, you need to supply a mask saying which
+channel range you want it to calculate the variance from.
+
+\begin{verbatim}
+ ASAP> msk = scans.create_mask([200,400],[600,800])
+ ASAP> av = average_time(scans, mask=msk, weight='var')
+\end{verbatim}
+
+If you have not observed your data with Doppler tracking (or run
+\cmd{freq\_align} explicitally) you should align the data in frequency
+before averaging.
+
+\begin{verbatim}
+ ASAP> av = scans.average_time(align=True)
+\end{verbatim}
+
+Note that, if needed, you should run \cmd{gain\_el} and \cmd{opacity}
+before you average the data in time (\S \ref{sec:gainel} \&
+\ref{sec:freqalign}).
+
+\subsection{Baseline fitting}
+
+\index{Baseline fitting}To make a baseline fit, you must first create
+a mask of channels to use in the baseline fit.
+
+\begin{verbatim}
+ ASAP> msk = scans.create_mask([100,400],[600,900])
+ ASAP> scans.poly_baseline(msk, order=1)
+\end{verbatim}
+
+This will fit a first order polynomial to the selected channels and subtract
+this polynomial from the full spectra.
+
+\subsubsection{Auto-baselining}
+
+\index{Auto-baseline}The function \cmd{auto\_poly\_baseline} can be used to automatically
+baseline your data without having to specify channel ranges for the
+line free data. It automatically figures out the line-free emission
+and fits a polynomial baseline to that data. The user can use masks to
+fix the range of channels or velocity range for the fit as well as
+mark the band edge as invalid.
+
+Simple example
+
+\begin{verbatim}
+  ASAP> scans.auto_poly_baseline(order=2,threshold=5)
+\end{verbatim}
+
+\cmd{order} is the polynomial order for the fit. \cmd{threshold} is
+the SNR threshold to use to deliminate line emission from
+signal. Generally the value of threshold is not too critical, however
+making this too large will compromise the fit (as it will include
+strong line features) and making it too small will mean it cannot find
+enough line free channels.
+
+
+Other examples:
+
+\begin{verbatim}
+  # Don't try and fit the edge of the bandpass which is noisier
+  ASAP> scans.auto_poly_baseline(edge=(500,450),order=3,threshold=3)
+
+  # Only fit a given region around the line
+  ASAP> scans.set_unit('km/s')
+  ASAP> msk = scans.create_mask([-60,-20])
+  ASAP> scans.auto_poly_baseline(mask=msk,order=3,threshold=3)
+
+\end{verbatim}
+
+\subsection{Average the polarisations}
+
+\index{average\_pol}If you are just interested in the highest SNR for total intensity you
+will want to average the parallel polarisations together.
+
+\begin{verbatim}
+ ASAP> scans.average_pol()
+\end{verbatim}
+
+\subsection{Calibration}
+
+\index{Calibration}For most uses, calibration happens transparently as the input data
+contains the Tsys measurements taken during observations. The nominal
+``Tsys'' values may be in Kelvin or Jansky. The user may wish to
+supply a Tsys correction or apply gain-elevation and opacity
+corrections.
+
+\subsubsection{Brightness Units}
+
+\index{Brightness Units}RPFITS files do not contain any information as
+to whether the telescope calibration was in units of Kelvin or
+Janskys.  On reading the data a default value is set depending on the
+telescope and frequency of observation.  If this default is incorrect
+(you can see it in the listing from the \cmd{summary} function) the
+user can either override this value on reading the data or later.
+E.g:
+
+\begin{verbatim}
+  ASAP> scans = scantable('2004-11-23_1841-P484.rpf', unit='Jy')
+  # Or in two steps
+  ASAP> scans = scantable('2004-11-23_1841-P484.rpf')
+  ASAP> scans.set_fluxunit('Jy)
+\end{verbatim}
+
+\subsubsection{Tsys scaling}
+
+\index{Tsys scaling}Sometime the nominal Tsys measurement at the
+telescope is wrong due to an incorrect noise diode calibration. This
+can easily be corrected for with the scale function. By default,
+\cmd{scale} only scans the spectra and not the corresponding Tsys.
+
+\begin{verbatim}
+  ASAP> scans.scale(1.05, tsys=True)
+\end{verbatim}
+
+\subsubsection{Unit Conversion}
+
+\index{Unit conversion}To convert measurements in Kelvin to Jy (and
+vice versa) the global function \cmd{convert\_flux} is needed. This
+converts and scales the data from K to Jy or vice-versa depending on
+what the current brightness unit is set to. The function knows the
+basic parameters for some frequencies and telescopes, but the user may
+need to supply the aperture efficiency, telescope diameter or the Jy/K
+factor.
+
+\begin{verbatim}
+  ASAP> scans.convert_flux()               # If efficency known
+  ASAP> scans.convert_flux(eta=0.48)       # If telescope diameter known
+  ASAP> scans.convert_flux(eta=0.48,d=35)  # Unknown telescope
+  ASAP> scans.convert_flux(jypk=15)        # Alternative
+\end{verbatim}
+
+\subsubsection{Gain-Elevation and Opacity Corrections}
+\label{sec:gainel}
+
+\index{Gain-elevation}As higher frequencies (particularly $>$20~GHz)
+it is important to make corrections for atmospheric opacity and
+gain-elevation effects.
+
+Note that currently the elevation is not written correctly into
+Tidbinbilla rpfits files. This means that gain-elevation and opacity
+corrections will not work unless these get recalculated.
+
+\begin{verbatim}
+  ASAP> scans.recalc_azel()                # recalculate az/el based on pointing
+\end{verbatim}
+
+Gain-elevation curves for some telescopes and frequencies are known to
+ASAP (currently only for Tidbinbilla at 20~GHz).  In these cases
+making gain-corrections is simple.  If the gain curve for your data is
+not known, the user can supply either a gain polynomial or text file
+tabulating gain factors at a range of elevations (see \cmd{help
+scantable.gain\_el}).
+
+Examples:
+
+\begin{verbatim}
+  ASAP> scans.gain_el()   # If gain table known
+  ASAP> scans.gain_el(poly=[3.58788e-1,2.87243e-2,-3.219093e-4])
+\end{verbatim}
+
+\index{Opacity}Opacity corrections can be made with the global
+function \cmd{opacity}. This should work on all telescopes as long as
+a measurement of the opacity factor was made during the observation.
+
+\begin{verbatim}
+  ASAP> scans.opacity(0.083)
+\end{verbatim}
+
+Note that at 3~mm Mopra uses a paddle wheel for Tsys calibration,
+which takes opacity effects into account (to first order). ASAP
+opacity corrections should not be used for Mopra 3-mm data.
+
+\subsection{Frequency Frame Alignment}
+\label{sec:freqalign}
+
+\index{Frequency alignment}\index{Velicity alignment}When time
+averaging a series of scans together, it is possible that the velocity
+scales are not exactly aligned.  This may be for many reasons such as
+not Doppler tracking the observations, errors in the Doppler tracking
+etc.  This mostly affects very long integrations or integrations
+averaged together from different days.  Before averaging such data
+together, they should be frequency aligned using \cmd{freq\_align}.
+
+E.g.:
+
+\begin{verbatim}
+  ASAP> scans.freq_align()
+  ASAP> av = average_time(scans)
+\end{verbatim}
+
+{\em A Global freq\_align command will be made eventually}
+
+To average together data taken on different days, which are in
+different scantables, each scantable must aligned to a common
+reference time then the scantables averaged. The simplest way of
+doing this is to allow ASAP to choose the reference time for the first
+scantable then using this time for the subsequent scantables.
+
+\begin{verbatim}
+  ASAP> scans1.freq_align() # Copy the refeference Epoch from the output
+  ASAP> scans2.freq_align(reftime='2004/11/23/18:43:35')
+  ASAP> scans3.freq_align(reftime='2004/11/23/18:43:35')
+  ASAP> av = average_time(scans1, scans2, scans3)
+\end{verbatim}
+
+\section{Scantable manipulation}
+
+\index{Scantable!manipulation}While it is very useful to have many
+independent sources within one scantable, it is often inconvenient for
+data processing. The \cmd{get\_scan} function can be used to create a
+new scantable with a selection of scans from a scantable. The
+selection can either be on the source name, with simple wildcard
+matching or set of scan ids. Internally this uses the selector object,
+so for more complicated selection the selector should be used directly
+instead.
+
+For example:
+
+\begin{verbatim}
+  ASAP> ss = scans.get_scan(10) # Get the 11th scan (zero based)
+  ASAP> ss = scans.get_scan(range(10)) # Get the first 10 scans
+  ASAP> ss = scans.get_scan(range(10,20)) # Get the next 10 scans
+  ASAP> ss = scans.get_scan([2,4,6,8,10]) # Get a selection of scans
+
+  ASAP> ss = scans.get_scan('345p407') # Get a specific source
+  ASAP> ss = scans.get_scan('345*')    # Get a few sources
+
+  ASAP> r = scans.get_scan('*_R') # Get all reference sources (Parkes/Mopra)
+  ASAP> s = scans.get_scan('*_S') # Get all program sources (Parkes/Mopra)
+  ASAP> r = scans.get_scan('*[ew]')  # Get all reference sources (Tid)
+  ASAP> s = scans.get_scan('*[^ew]') # Get all program sources (Tid)
+
+\end{verbatim}
+
+To copy a scantable the following does not work:
+
+\begin{verbatim}
+  ASAP> ss = scans
+\end{verbatim}
+
+as this just creates a reference to the original scantable. Any
+changes made to \cmd{ss} are also seen in \cmd{scans}. To duplicate a
+scantable, use the copy function.
+
+\begin{verbatim}
+  ASAP> ss = scans.copy()
+\end{verbatim}
+
+\section{Data Output}
+
+\index{Scantable!save}\index{Saving data}ASAP can save scantables in a
+variety of formats, suitable for reading into other packages. The
+formats are:
+
+\begin{itemize}
+\item[ASAP] This is the internal format used for ASAP. It is the only
+  format that allows the user to restore the data, fits etc. without
+  loosing any information.  As mentioned before, the ASAP scantable is
+  an AIPS++ Table (a memory-based table).  This function just converts
+  it to a disk-based Table.  You can the access that Table with the
+  AIPS++ Table browser or any other AIPS++ tool.
+
+\item[SDFITS] The Single Dish FITS format. This format was designed to
+  for interchange between packages, but few packages actually can read
+  it.
+
+%\item[FITS] This uses simple ``image'' fits to save the data, each row
+%  being written to a separate fits file. This format is suitable for
+%  importing the data into CLASS.
+
+\item[ASCII] A simple text based format suitable for the user to
+processing using Perl or, Python, gnuplot etc.
+
+\item[MS2] Saves the data in an aips++ MeasurementSet V2 format.
+You can also access this with the Table browser and other AIPS++
+tools.
+
+\end{itemize}
+
+The default output format can be set in the users {\tt .asaprc} file.
+Typical usages are:
+
+\begin{verbatim}
+  ASAP> scans.save('myscans') # Save in default format
+  ASAP> scans.save('myscans', 'FITS') # Save as FITS for exporting into CLASS
+  ASAP> scans.save('myscans', overwrite=True) # Overwrite an existing file
+\end{verbatim}
+
+
+\section{Plotter}
+
+\index{Plotter}Scantable spectra can be plotted at any time. An asapplotter object is
+used for plotting, meaning multiple plot windows can be active at the
+same time. On start up a default asapplotter object is created called
+``plotter''. This would normally be used for standard plotting.
+
+The plotter, optionally, will run in a multipanel mode and contain
+multiple plots per panel. The user must tell the plotter how they want
+the data distributed. This is done using the set\_mode function. The
+default can be set in the users {\tt .asaprc} file. The units (and frame
+etc) of the abscissa will be whatever has previously been set by
+\cmd{set\_unit}, \cmd{set\_freqframe} etc.
+
+Typical plotter usage would be:
+
+\begin{verbatim}
+  ASAP> scans.set_unit('km/s')
+  ASAP> plotter.set_mode(stacking='p',panelling='t')
+  ASAP> plotter.plot(scans)
+\end{verbatim}
+
+This will plot multiple polarisation within each plot panel and each
+scan row in a separate panel.
+
+Other possibilities include:
+
+\begin{verbatim}
+  # Plot multiple IFs per panel
+  ASAP> plotter.set_mode(stacking='i',panelling='t')
+
+  # Plot multiple beams per panel
+  ASAP> plotter.set_mode(stacking='b',panelling='t')
+
+  # Plot one IF per panel, time stacked
+  ASAP> plotter.set_mode('t', 'i')
+
+  # Plot each scan in a seperate panel
+  ASAP> plotter.set_mode('t', 's')
+
+\end{verbatim}
+
+\subsection{Plot Selection}
+\label{sec:plotter_cursor}
+
+\index{Plotter!selection}The plotter can plot up to 25 panels and
+stacked spectra per panel. If you have data larger than this (or for
+your own sanity) you need to select a subset of this data. This is
+particularly true for multibeam or multi IF data. The selector object
+should be used for this purpose. Selection can either be applied to
+the scantable or directly to the plotter, the end result is the same.
+You don't have to reset the scantable slection though, if you set
+the selection on the plotter.
+
+Examples:
+
+\begin{verbatim}
+  ASAP> selection = selector()
+  # Select second IF
+  ASAP> selection.set_ifs(1)
+  ASAP> plotter.set_selection(selection)
+
+  # Select first 4 beams
+  ASAP> selection.set_beams([0,1,2,3])
+  ASAP> plotter.set_selection(selection)
+
+  # Select a few scans
+  ASAP> selection.set_scans([2,4,6,10])
+  ASAP> plotter.set_selection(selection)
+
+  # Multiple selection
+  ASAP> selection.set_ifs(1)
+  ASAP> selection.set_scans([2,4,6,10])
+  ASAP> plotter.set_selection(selection)
+
+\end{verbatim}
+
+\subsection{Plot Control}
+
+\index{Plotter!control}The plotter window has a row of buttons on the
+lower left. These can be used to control the plotter (mostly for
+zooming the individual plots). From left to right:
+
+\begin{itemize}
+
+\item[Home] This will unzoom the plots to the original zoom factor
+
+\item[Plot history] (left and right arrow). The plotter keeps a
+history of zoom settings. The left arrow sets the plot zoom to the
+previous value. The right arrow returns back again. This allows you,
+for example, to zoom in on one feature then return the plot to how it
+was previously.
+
+\item[Pan] (The Cross) This sets the cursor to pan, or scroll mode
+  allowing you to shift the plot within the window. Useful when
+  zoomed in on a feature.
+
+\item[Zoom] (the letter with the magnifying glass) lets you draw a
+  rectangle around a region of interest then zooms in on that
+  region. Use the plot history to unzoom again.
+
+\item[Adjust] (rectangle with 4 arrows) adjust subplot paramaters
+  (space at edge of plots)
+
+\item[Save] (floppy disk). Save the plot as a postscript or .png file
+
+You can also type ``g'' in the plot window to toggle on and off grid
+lines. Typing 'l' turns on and off logarithmic Y-axis.
+
+\end{itemize}
+
+\subsection{Other control}
+
+The plotter has a number of functions to describe the layout of the
+plot. These include \cmd{set\_legend}, \cmd{set\_layout} and \cmd{set\_title}.
+
+To set the exact velocity or channel range to be plotted use the
+\cmd{set\_range} function. To reset to the default value, call
+\cmd{set\_range} with no arguments. E.g.
+
+\begin{verbatim}
+  ASAP> scans.set_unit('km/s')
+  ASAP> plotter.plot(scans)
+  ASAP> plotter.set_range(-150,-50)
+  ASAP> plotter.set_range() # To reset
+\end{verbatim}
+
+Both the range of the ``x'' and ``y'' axis can be set at once, if desired:
+
+\begin{verbatim}
+  ASAP> plotter.set_range(-10,30,-1,6.6)
+\end{verbatim}
+
+To save a hardcopy of the current plot, use the save function, e.g.
+
+\begin{verbatim}
+  ASAP> plotter.save('myplot.ps')
+  ASAP> plotter.save('myplot.png', dpi=80)
+\end{verbatim}
+
+\section{Fitting}
+
+\index{Fitting}Currently multicomponent Gaussian function is
+available. This is done by creating a fitting object, setting up the
+fit and actually fitting the data. Fitting can either be done on a
+single scantable selection or on an entire scantable using the
+\cmd{auto\_fit} function. If single value fitting is used, and the
+current selection includes multiple spectra (beams, IFs, scans etc)
+then the first spectrum in the scantable will be used for fitting.
+
+\begin{verbatim}
+ ASAP> f = fitter()
+ ASAP> f.set_function(gauss=2) # Fit two Gaussians
+ ASAP> f.set_scan(scans)
+ ASAP> selection = selector()
+ ASAP> selection.set_polarisations(1) # Fit the second polarisation
+ ASAP> scans.set_selection(selection)
+ ASAP> scans.set_unit('km/s')  # Make fit in velocity units
+ ASAP> f.fit(1)                # Run the fit on the second row in the table
+ ASAP> f.plot()                # Show fit in a plot window
+ ASAP> f.get_parameters()      # Return the fit paramaters
+\end{verbatim}
+
+This auto-guesses the initial values of the fit and works well for data
+without extra confusing features. Note that the fit is performed in
+whatever unit the abscissa is set to.
+
+If you want to confine the fitting to a smaller range (e.g. to avoid
+band edge effects or RFI you must set a mask.
+
+\begin{verbatim}
+  ASAP> f = fitter()
+  ASAP> f.set_function(gauss=2)
+  ASAP> scans.set_unit('km/s')  # Set the mask in channel units
+  ASAP> msk = s.create_mask([1800,2200])
+  ASAP> scans.set_unit('km/s')  # Make fit in velocity units
+  ASAP> f.set_scan(s,msk)
+  ASAP> f.fit()
+  ASAP> f.plot()
+  ASAP> f.get_parameters()
+\end{verbatim}
+
+If you wish, the initial parameter guesses can be specified and
+specific parameters can be fixed:
+
+\begin{verbatim}
+  ASAP> f = fitter()
+  ASAP> f.set_function(gauss=2)
+  ASAP> f.set_scan(s,msk)
+  ASAP> f.fit() # Fit using auto-estimates
+  # Set Peak, centre and fwhm for the second gaussian.
+  # Force the centre to be fixed
+  ASAP> f.set_gauss_parameters(0.4,450,150,0,1,0,component=1)
+  ASAP> f.fit() # Re-run the fit
+\end{verbatim}
+
+The fitter \cmd{plot} function has a number of options to either view
+the fit residuals or the individual components (by default it plots
+the sum of the model components).
+
+Examples:
+
+\begin{verbatim}
+  # Plot the residual
+  ASAP> f.plot(residual=True)
+
+  # Plot the first 2 componentsa
+  ASAP> f.plot(components=[0,1])
+
+  # Plot the first and third component plus the model sum
+  ASAP> f.plot(components=[-1,0,2])  # -1 means the compoment sum
+\end{verbatim}
+
+\subsection{Fit saving}
+
+\index{Fitter!Fit saving}One you are happy with your fit, it is
+possible to store it as part of the scantable.
+
+\begin{verbatim}
+  ASAP> f.storefit()
+\end{verbatim}
+
+This will be saved to disk with the data, if the ``ASAP'' file format
+is selected. Multiple fits to the same data can be stored in the
+scantable.
+
+The scantable function \cmd{get\_fit} can be used to retrieve the
+stored fits. Currently the fit parameters are just printed to the
+screen.
+
+\begin{verbatim}
+  ASAP> scans.get_fit(4) # Print fits for row 4
+\end{verbatim}
+
+\section{Polarisation}
+
+\index{Polarisation}Currently ASAP only supports polarmetric analysis
+on linearly polarised feeds and the cross polarisation products
+measured. Other cases will be added on an as needed basis.
+
+Conversions of linears to Stokes or Circular polarisations are done
+``on-the-fly''. Leakage cannot be corrected for nor are there routines
+to calibrate position angle offsets.
+
+\subsection{Simple Calibration}
+
+\index{Polarisation!calibration}It is possible that there is a phase
+offset between polarisation which will effect the phase of the cross
+polarisation correlation, and so give rise to spurious
+polarisation. \cmd{rotate\_xyphase} can be used to correct for this
+error. At this point, the user must know how to determine the size of
+the phase offset themselves.
+
+\begin{verbatim}
+  ASAP> scans.rotate_xyphase(10.5)            # Degrees
+\end{verbatim}
+
+Note that if this function is run twice, the sum of the two values is
+applied because it is done in-situ.
+
+A correction for the receiver parallactic angle may need to be made,
+generally because of how it is mounted. Use \cmd{rotate\_linpolphase}
+to correct the position angle. Running this function twice results in
+the sum of the corrections being applied because it is applied
+in-situ.
+
+\begin{verbatim}
+  ASAP> scans.rotate_linpolphase(-45) # Degrees; correct for receiver mounting
+\end{verbatim}
+
+If the sign of the complex correlation is wrong (this can happen
+depending on the correlator configuration), use \cmd{invert\_phase} to
+change take the complex conjugate of the complex correlation
+term. This is always performed in-situ.
+
+\begin{verbatim}
+  ASAP> scans.invert_phase()
+\end{verbatim}
+
+Depending on how the correlator is configured, ``BA'' may be
+correlated insead of ``AB''. Use \cmd{swap\_linears} to correct for
+this problem:
+
+\begin{verbatim}
+  ASAP> scans.swap_linears()
+\end{verbatim}
+
+\subsection{Conversion}
+\label{sec:polconv}
+
+Data can be permanently converted between linear and circular
+polarisations and stokes.
+
+\begin{verbatim}
+  ASAP> stokescans = linearscans.convert_pol("stokes")
+\end{verbatim}
+
+
+\subsection{Plotting}
+\label{sec:polplot}
+
+\index{Polarisation!plotting}To plot Stokes values, a selector object
+must be created and the set\_polarisation function used to select the
+desired polarisation products.
+
+The values which can be plotted include a selection of [I,Q,U,V], [I,
+Plinear, Pangle, V], [RR, LL] or [XX, YY, Real(XY),
+Imaginary(XY)]. (Plinear and Pangle are the percentage and position
+angle of linear polarisation).
+
+Example:
+
+\begin{verbatim}
+  ASAP> selection = selector()
+
+  ASAP> selection.set_polarisations(``I Q U V'')
+  ASAP  plotter.set_selection(selection);              # Select I, Q, U \& V
+
+  ASAP> selection.set_polarisations(``I Q'')
+  ASAP  plotter.set_selection(selection);              # Select just I \& Q
+
+  ASAP> selection.set_polarisations(``RR LL'')
+  ASAP  plotter.set_selection(selection);              # Select just RR \& LL
+
+  ASAP> selection.set_polarisations(``XX YY'')
+  ASAP  plotter.set_selection(selection);              # Select linears
+
+  ASAP> selection.set_polarisations(``I Plinear'')
+  ASAP  plotter.set_selection(selection);              # Fractional linear
+
+  ASAP> selection.set_polarisations(``Pangle'')
+  ASAP  plotter.set_selection(selection);              # Position angle
+
+\end{verbatim}
+
+Scan, beam and IF selection are also available in the selector object as
+describe in section~\ref{sec:selection}.
+
+\subsection{Saving}
+
+\index{Polarisation!saving}When saving data using the \cmd{save}
+function, the \cmd{stokes} argument can be used to save the data as
+Stoke values when saving in FITS format.
+
+Example:
+
+\begin{verbatim}
+  ASAP> scans.save('myscan.sdfits', 'SDFITS', stokes=True)
+\end{verbatim}
+
+\section{Scantable Mathematics}
+
+\index{Scantable!maths}It is possible to to simple mathematics
+directly on scantables from the command line using the \cmd{+, -, *,
+/} operators as well as their cousins \cmd{+=, -= *=, /=}. This works
+between a scantable and a float. (Note that it does
+not work for integers).
+
+{\em Currently mathematics between two scantables is not available }
+
+%  ASAP> sum = scan1+scan2
+\begin{verbatim}
+  ASAP> scan2 = scan1+2.0
+  ASAP> scan *= 1.05
+\end{verbatim}
+
+\section{Scripting}
+
+\index{Scripting}Because asap is based on python, it easy for the user
+write their own scripts and functions to process data. This is highly
+recommended as most processing of user data could then be done in a
+couple of steps using a few simple user defined functions. A Python
+primer is beyond the scope of this userguide. See the asap home pages
+for a scripting tutorial or the main python website for comprehensive
+documentation.
+
+\hspace{1cm} http://www.atnf.csiro.au/computing/software/asap/tutorials
+
+\hspace{1cm} http://www.python.org/doc/Introduction.html
+
+\subsection{Running scripts}
+
+The asap global function \cmd{execfile} reads the named text file and
+executes the contained python code. This file can either contain
+function definitions which will be used in subsequent processing or
+just a set of commands to process a specific dataset.
+
+\subsection{asapuserfuncs.py}
+
+The file $\sim$/.asap/asapuserfuncs.py is automatically read in when
+asap is started. The user can use this to define a set of user
+functions which are automatically available each time asap is
+used. The \cmd{execfile} function can be called from within this file.
+
+\section{Worked examples}
+
+In the following section a few examples of end-to-end processing of
+some data in asap are given.
+
+\subsection{Mopra}
+\index{Mopra}
+
+The following example is of some dual polarisation, position switched
+data from Mopra. The source has been observed mulitple times split
+into a number of seperate rpfits files. To make the processing easier,
+the first step is to \cmd{cat} the seeprate rpfits files together and
+load as a whole (future versions of asap will make this unnecessary).
+
+
+\begin{verbatim}
+# get a list of the individual rpfits files in the current directory
+myfiles = list_files()
+
+# Load the data into a scantable
+data = scantable(myfiles)
+print data
+
+# Form the quotient spectra
+q = data.auto_quotient()
+print q
+
+# Look at the spectra
+plotter.plot(q)
+
+# Set unit and reference frame
+q.set_unit('km/s')
+q.set_freqframe('LSRK')
+
+# Average all scans in time, aligning in velocity
+av = q.average_time(align=True)
+plotter.plot(av)
+
+# Remove the baseline
+msk = av.create_mask([100,130],[160,200])
+av.poly_baseline(msk,2)
+
+# Average the two polarisations together
+iav = av.average_pol()
+print iav
+plotter.plot(iav)
+
+# Set a sensible velocity range on the plot
+plotter.set_range(85,200)
+
+# Smooth the data a little
+av.smooth('gauss',4)
+plotter.plot()
+
+# Fit a guassian to the emission
+f = fitter()
+f.set_function(gauss=1)
+f.set_scan(av)
+f.fit()
+
+# View the fit
+f.plot()
+
+# Get the fit parameters
+f.get_parameters()
+
+\end{verbatim}
+
+
+\subsection{Parkes Polarimetry}
+
+\index{Parkes}\index{Polarisation}The following example is processing
+of some Parkes polarmetric observations of OH masers at
+1.6~GHz. Because digital filters where used in the backend, the
+baselines are stable enough not to require a quotient spectra. The
+4~MHz bandwidth is wide enough to observe both the 1665 and 1667~MHz
+OH maser transitions. Each source was observed once for about 10
+minutes. Tsys information was not written to the rpfits file (a
+nominal 25K values was used), so the amplitudes need to be adjusted
+based on a separate log file. A simple user function is used to
+simplify this, contained in a file called mypol.py:
+
+\begin{verbatim}
+def xyscale(data,xtsys=1.0,ytsys=1.0,nomtsys=25.0) :
+
+ selection = selector()
+ selection.set_polarisations(0)
+ data.set_selection(selection)
+ data.scale(xtsys/nomtsys)
+
+ selection.set_polarisations(1)
+ data.set_selection(selection)
+ data.scale(ytsys/nomtsys)
+
+ selection.set_polarisations(0)
+ data.set_selection(selection)
+ data.scale((xtsys+ytsys)/(2*nomtsys))
+
+ selection.set_polarisations(0)
+ data.set_selection(selection)
+ data.scale((xtsys+ytsys)/(2*nomtsys))
+\end{verbatim}
+
+The typical asap session would be
+
+\begin{verbatim}
+
+# Remind ourself the name of the rpfits files
+ls
+
+# Load data from an rpfits file
+d1665 = scantable('2005-10-27_0154-P484.rpf')
+
+# Check what we have just loaded
+d1665.summary()
+
+# View the data in velocity
+d1665.set_unit('km/s')
+d1665.set_freqframe('LSRK')
+
+# Correct for the known phase offset in the crosspol data
+d1665.rotate_xyphase(-4)
+
+# Create a copy of the data and set the rest frequency to the 1667 MHz
+# transition
+d1667 = d1665.copy()
+d1667.set_restfreqs([1667.3590], 'MHz')
+d1667.summary()
+
+# Copy out the scan we wish to process
+g351_5 = d1665.get_scan('351p160')
+g351_7 = d1667.get_scan('351p160')
+
+# Baseline both
+msk = g351_5.create_mask([-30,-25],[-5,0])
+g351_5.poly_baseline(msk,order=1)
+msk = g351_7.create_mask([-30,-25],[-5,0])
+g351_7.poly_baseline(msk,order=1)
+
+
+# Plot the data. The plotter can only plot a single scantable
+# So we must merge the two tables first
+
+plotscans = merge(g351_5, g351_7)
+
+plotter.plot(plotscans) # Only shows one panel
+
+# Tell the plotter to stack polarisation and panel scans
+plotter.set_mode('p','s')
+
+# Correct for the Tsys using our predefined function
+execfile('mypol.py') # Read in the function xyscale
+xyscale(g351_5,23.2,22.7) # Execute it on the data
+xyscale(g351_7,23.2,22.7)
+
+# Only plot the velocity range of interest
+plotter.set_range(-30,10)
+
+# Update the plot with the baselined data
+plotter.plot()
+
+# Look at the various polarisation products
+selection = selector()
+selection.set_polarisations(``RR LL'')
+plotter.set_selection(selection)
+selection.set_polarisations(``I Plinear'')
+plotter.set_selection(selection)
+selection.set_polarisations(``I Q U V'')
+plotter.set_selection(selection)
+
+# Save the plot as postscript
+plotter.save('g351_stokes.ps')
+
+# Save the process spectra
+plotscans.save('g351.asap')
+
+\end{verbatim}
+
+\subsection{Tidbinbilla}
+
+\index{Tidbinbilla}The following example is processing of some
+Tidbinbilla observations of NH$_3$ at 12~mm. Tidbinbilla has (at the
+time of observations) a single polarisation, but can process two IFs
+simultaneously. In the example, the first half of the observation was
+observing the (1,1) and (2,2) transitions simultaneously). The second
+half observed only the (4,4) transition due to bandwidth
+limitations. The data is position switched, observing first an
+reference to the west, then the source twice and finally reference to
+the east.
+
+\begin{verbatim}
+
+# Load the rpfits file and inspect
+d = scantable('2003-03-16_082048_t0002.rpf')
+print d
+
+# Make the quotient spectra
+q = d.auto_quotient()
+print q
+
+del d
+
+# Plot/select in velocity
+q.set_freqframe('LSRK')
+q.set_unit('km/s')
+
+# Correct for gain/el effects
+
+q.recalc_azel()  # Tid does not write the elevation
+q.gain_el()
+q.opacity(0.05)
+
+# Seperate data from the (1,1)&(2,2) and (4,4) transitions
+g1 = q.get_scan(range(6))     # scans 0..5
+g2 = q.get_scan(range(6,12))  # scans 6..11
+
+# Align data in velocity
+g1.freq_align()
+g2.freq_align()
+
+# Average individual scans
+a1 = g1.average_time()
+a2 = g2.average_time()
+
+# Rpfits file only contains a single rest frequency. Set both
+a1.set_restfreqs([23694.4700e6,23722.6336e6])
+
+plotter.plot(a1)
+plotter.set_mode('i','t')
+
+a1.auto_poly_baseline()
+
+plotter.plot()
+
+a1.smooth('gauss',5)
+plotter.plot()
+
+
+\end{verbatim}
+
+\newpage
+
+\section{Appendix}
+
+\subsection{Function Summary}
+
+\index{Functions!summary}%
+\begin{verbatim}
+ASAP> commands()
+    [The scan container]
+        scantable           - a container for integrations/scans
+                              (can open asap/rpfits/sdfits and ms files)
+            copy            - returns a copy of a scan
+            get_scan        - gets a specific scan out of a scantable
+                              (by name or number)
+            set_selection   - set a new subselection of the data
+            get_selection   - get the current selection object
+            summary         - print info about the scantable contents
+            stats           - get specified statistic of the spectra in
+                              the scantable
+            stddev          - get the standard deviation of the spectra
+                              in the scantable
+            get_tsys        - get the TSys
+            get_time        - get the timestamps of the integrations
+            get_sourcename  - get the source names of the scans
+            get_azimuth     - get the azimuth of the scans
+            get_elevation   - get the elevation of the scans
+            get_parangle    - get the parallactic angle of the scans
+            get_unit        - get the current unit
+            set_unit        - set the abcissa unit to be used from this
+                              point on
+            get_abcissa     - get the abcissa values and name for a given
+                              row (time)
+            set_freqframe   - set the frame info for the Spectral Axis
+                              (e.g. 'LSRK')
+            set_doppler     - set the doppler to be used from this point on
+            set_dirframe    - set the frame for the direction on the sky
+            set_instrument  - set the instrument name
+            get_fluxunit    - get the brightness flux unit
+            set_fluxunit    - set the brightness flux unit
+            create_mask     - return an mask in the current unit
+                              for the given region. The specified regions
+                              are NOT masked
+            get_restfreqs   - get the current list of rest frequencies
+            set_restfreqs   - set a list of rest frequencies
+            flag_spectrum   - flag data
+            save            - save the scantable to disk as either 'ASAP'
+                              or 'SDFITS'
+            nbeam,nif,nchan,npol - the number of beams/IFs/Pols/Chans
+            nscan           - the number of scans in the scantable
+            nrow            - te number of spectra in the scantable
+            history         - print the history of the scantable
+            get_fit         - get a fit which has been stored witnh the data
+            average_time    - return the (weighted) time average of a scan
+                              or a list of scans
+            average_pol     - average the polarisations together.
+                              The dimension won't be reduced and
+                              all polarisations will contain the
+                              averaged spectrum.
+            convert_pol     - convert to a different polarisation type
+            auto_quotient   - return the on/off quotient with
+                              automatic detection of the on/off scans
+                              (matched pairs and 1 off - n on)
+            scale           - return a scan scaled by a given factor
+            add             - return a scan with given value added
+            bin             - return a scan with binned channels
+            resample        - return a scan with resampled channels
+            smooth          - return the spectrally smoothed scan
+            poly_baseline   - fit a polynomial baseline to all Beams/IFs/Pols
+            auto_poly_baseline - automatically fit a polynomial baseline
+            recalc_azel     - recalculate azimuth and elevation based on
+                              the pointing
+            gain_el         - apply gain-elevation correction
+            opacity         - apply opacity correction
+            convert_flux    - convert to and from Jy and Kelvin brightness
+                              units
+            freq_align      - align spectra in frequency frame
+            rotate_xyphase  - rotate XY phase of cross correlation
+            rotate_linpolphase - rotate the phase of the complex
+                                 polarization O=Q+iU correlation
+            freq_switch     - perform frequency switching on the data
+            stats           - Determine the specified statistic, e.g. 'min'
+                              'max', 'rms' etc.
+            stddev          - Determine the standard deviation of the current
+                              beam/if/pol
+
+     [Math] Mainly functions which operate on more than one scantable
+
+            average_time    - return the (weighted) time average
+                              of a list of scans
+            quotient        - return the on/off quotient
+            simple_math     - simple mathematical operations on two scantables,
+                              'add', 'sub', 'mul', 'div'
+     [Fitting]
+        fitter
+            auto_fit        - return a scan where the function is
+                              applied to all Beams/IFs/Pols.
+            commit          - return a new scan where the fits have been
+                              commited.
+            fit             - execute the actual fitting process
+            store_fit       - store the fit parameters in the data (scantable)
+            get_chi2        - get the Chi^2
+            set_scan        - set the scantable to be fit
+            set_function    - set the fitting function
+            set_parameters  - set the parameters for the function(s), and
+                              set if they should be held fixed during fitting
+            set_gauss_parameters - same as above but specialised for individual
+                                   gaussian components
+            get_parameters  - get the fitted parameters
+            plot            - plot the resulting fit and/or components and
+                              residual
+    [Plotter]
+        asapplotter         - a plotter for asap, default plotter is
+                              called 'plotter'
+            plot            - plot a scantable
+            save            - save the plot to a file ('png' ,'ps' or 'eps')
+            set_mode        - set the state of the plotter, i.e.
+                              what is to be plotted 'colour stacked'
+                              and what 'panelled'
+            set_selection   - only plot a selected part of the data
+            set_range       - set a 'zoom' window [xmin,xmax,ymin,ymax]
+            set_legend      - specify user labels for the legend indeces
+            set_title       - specify user labels for the panel indeces
+            set_abcissa     - specify a user label for the abcissa
+            set_ordinate    - specify a user label for the ordinate
+            set_layout      - specify the multi-panel layout (rows,cols)
+            set_colors      - specify a set of colours to use
+            set_linestyles  - specify a set of linestyles to use if only
+                              using one color
+            set_mask        - set a plotting mask for a specific polarization
+
+    [Reading files]
+        reader              - access rpfits/sdfits files
+            open            - attach reader to a file
+            close           - detach reader from file
+            read            - read in integrations
+            summary         - list info about all integrations
+
+    [General]
+        commands            - this command
+        print               - print details about a variable
+        list_scans          - list all scantables created bt the user
+        list_files          - list all files readable by asap (default rpf)
+        del                 - delete the given variable from memory
+        range               - create a list of values, e.g.
+                              range(3) = [0,1,2], range(2,5) = [2,3,4]
+        help                - print help for one of the listed functions
+        execfile            - execute an asap script, e.g. execfile('myscript')
+        list_rcparameters   - print out a list of possible values to be
+                              put into $HOME/.asaprc
+        mask_and,mask_or,
+        mask_not            - boolean operations on masks created with
+                              scantable.create_mask
+
+    Note:
+        How to use this with help:
+                                         # function 'summary'
+        [xxx] is just a category
+        Every 'sub-level' in this list should be replaces by a '.' Period when
+        using help
+        Example:
+            ASAP> help scantable # to get info on ths scantable
+            ASAP> help scantable.summary # to get help on the scantable's
+            ASAP> help average_time
+
+
+\end{verbatim}
+
+\subsection{ASCII output format}
+
+\subsection{.asaprc settings}
+\index{.asaprc}
+\asaprc{verbose}{{\bf True}/False}{Print verbose output, good to disable in scripts}
+
+\asaprc{insitu}{{\bf True}/False}{Apply operations on the input
+scantable or return new one}
+
+% plotting
+
+\asaprc{useplotter}{{\bf True}/False}{Preload a default plotter}
+
+\asaprc{plotter.gui}{{\bf True}/False}{Do we want a GUI or plot to a
+file}
+
+\asaprc{plotter.stacking}{{\bf Pol} Beam IF Scan Time}{Default mode for
+colour stacking}
+
+\asaprc{plotter.panelling}{Pol Beam IF {\bf Scan} Time}{Default mode
+for panelling}
+
+\asaprc{plotter.ganged}{{\bf True}/False}{Push panels together, to
+share axislabels}
+
+\asaprc{plotter.decimate}{True/{\bf False}}{Decimate the number of
+points plotted by a factor of nchan/1024}
+
+% default colours/linestyles
+%\asaprc{plotter.colours}{.}{.}
+%\asaprc{plotter.linestyles{.}{.}
+
+% scantable
+\asaprc{scantable.save}{{\bf ASAP} SDFITS FITS ASCII MS2}{Default output
+format when saving}
+
+\asaprc{scantable.autoaverage}{{\bf True}/False}{Auto averaging on
+read}
+
+\asaprc{scantable.freqframe}{{\bf LSRK} TOPO BARY etc}{default
+frequency frame to set when function scantable.set\_freqframe is
+called or the data is imported}
+
+\subsection{Installation}
+
+\index{Installation}ASAP depends on a number of third-party libraries which you must
+have installed before attempting to build ASAP. These are:
+
+\begin{itemize}
+\item AIPS++
+\item Boost
+\item Matplotlib
+\item python/ipython
+\end{itemize}
+
+Debian Linux is currently supported and we intend also
+to support other popular Linux flavours, Solaris and Mac.
+
+Of the dependencies, AIPS++ is the most complex to install.
+
+\printindex
+
+\end{document}
+
Index: tags/Release2.1.0b/external/casa_asap/build.sh
===================================================================
--- tags/Release2.1.0b/external/casa_asap/build.sh	(revision 1196)
+++ tags/Release2.1.0b/external/casa_asap/build.sh	(revision 1196)
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+. aipsinit.sh
+
+MODULES="casa components coordinates fits images lattices measures ms scimath tables atnf"
+
+echo 'Building modules. This might take a while.'
+echo 'Progress can be seen with  tail -f build.log'
+if [ -f 'build.log' ]
+then
+    rm build.log
+    touch build.log
+fi
+for mod in ${MODULES}
+do
+    cd $ROOTDIR/code/$mod
+    echo "Making module $mod..."
+    make cleansys >> $ROOTDIR/build.log 2>&1
+    make >> $ROOTDIR/build.log 2>&1
+done
+echo 'Finished building the casa modules required by ASAP'
+echo 'Look for status in build.log' 
Index: tags/Release2.1.0b/external/casa_asap/install.sh
===================================================================
--- tags/Release2.1.0b/external/casa_asap/install.sh	(revision 1196)
+++ tags/Release2.1.0b/external/casa_asap/install.sh	(revision 1196)
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+ROOTDIR=`pwd`
+
+# insert flags to make it install with gcc-4.1
+gv=`gcc -dumpversion | sed -e 's#\.# #g'`
+major=`/bin/echo $gv | awk '{ print $1 }'`
+minor=`/bin/echo $gv | awk '{ print $2 }'`
+cppflags=''
+if [ $major -eq 4 ] && [ $minor -ge 1 ]; then
+    cppflags='-ffriend-injection -fpermissive '
+elif [ $major -eq 2 ]; then
+    echo "gcc version < 3 not supported"
+    exit 1
+fi
+
+for AIPSINIT in aipsinit.sh aipsinit.csh
+do
+  if [ ! -f $AIPSINIT ]
+  then
+    sed -e "s#__AIPSROOT#$ROOTDIR#g" $AIPSINIT.template > $AIPSINIT
+  fi
+done
+
+# detect 64bit
+platf=`uname -m | grep '64'`
+arch=`uname -s`
+
+if [ ! $platf == '' ]; then
+    mv $ROOTDIR/linux $ROOTDIR/linux_64b
+    cd $ROOTDIR/linux_64b/UNKNOWN_SITE
+    cat makedefs.64 | sed -e  "s#-Wall  #-Wall $flags#" > makedefs
+    cd $ROOTDIR
+elif [ $arch == 'Darwin' ]; then
+    mv $ROOTDIR/linux $ROOTDIR/darwin
+    cd $ROOTDIR/darwin/UNKNOWN_SITE
+    cat makedefs.darwin > makedefs
+    cd $ROOTDIR
+else
+    cd $ROOTDIR/linux/UNKNOWN_SITE
+    cat makedefs.32 | sed -e  "s#-Wall  #-Wall $flags#" > makedefs
+    cd $ROOTDIR
+fi
Index: tags/Release2.1.0b/external/casa_asap/linux/UNKNOWN_SITE/makedefs.32
===================================================================
--- tags/Release2.1.0b/external/casa_asap/linux/UNKNOWN_SITE/makedefs.32	(revision 1196)
+++ tags/Release2.1.0b/external/casa_asap/linux/UNKNOWN_SITE/makedefs.32	(revision 1196)
@@ -0,0 +1,152 @@
+#-----------------------------------------------------------------------------
+# makedefs: Installation-specific definitions used by AIPS++ makefiles
+#-----------------------------------------------------------------------------
+#
+#   Copyright (C) 1997,1998,1999,2000,2001,2003
+#   Associated Universities, Inc. Washington DC, USA.
+#
+#   This program is free software; you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation; either version 2 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program; if not, write to the Free Software
+#   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+#   Correspondence concerning AIPS++ should be addressed as follows:
+#          Internet email: aips2-request@nrao.edu.
+#          Postal address: AIPS++ Project Office
+#                          National Radio Astronomy Observatory
+#                          520 Edgemont Road
+#                          Charlottesville, VA 22903-2475 USA
+#
+#-----------------------------------------------------------------------------
+# This GNU makefile is read in by the makedefs file in the directory above
+# this.  That "default makedefs" file defines default values of each and
+# every variable required by the AIPS++ makefiles.  Those default values are
+# overridden where necessary by redefining them in this file.
+#
+# Installation instructions:
+#
+#    A template version of this file was copied from the "install/linux_gnu"
+#    directory into the site-specific AIPS++ system directory for this
+#    architecture (this directory) by the 'configure' script as part of the
+#    AIPS++ installation.
+#
+#    While default definitions are made in the default makedefs file in the
+#    directory above this, certain platform-specific redefinitions may have
+#    been included here to help make the installation easier.
+#
+#    You must carefully check all of the definitions made in the default
+#    makedefs file (as well as the redefinitions made here) and if necessary,
+#    correct them by making an overriding definition (or correction) here.
+#    However,
+#
+#                  DO NOT MODIFY THE DEFAULT MAKEDEFS FILE -
+#
+#                         MODIFY THIS FILE INSTEAD
+#
+#    Certain of the defaults in the default makedefs file are expressed in
+#    terms of another default using "recursively expanded" variables (defined
+#    with '=') rather than "simply expanded" (defined with ':=').  For
+#    example the TeX variables are defined in terms of "TEXROOT".  This may
+#    allow this makedefs file to redefine a whole set of variables, for
+#    example all of the TeX variables, simply by redefining one, TEXROOT.
+#    However,
+#
+#           ALWAYS USE "SIMPLY EXPANDED" VARIABLES WITHIN THIS FILE
+#
+#    That is, always use ":=" to make the assignments.
+#
+# Original: 1996/08/30 by Mark Calabretta, ATNF
+# $Id: makedefs.template,v 19.3 2006/05/25 15:49:39 wyoung Exp $
+#=============================================================================
+
+# General external libraries. 
+#---------------------------- 
+
+# X11.
+X11ROOT := /usr/X11R6
+
+# Motif (lesstif is fine).
+MotifROOT := $(X11ROOT)/LessTif/Motif2.1
+
+# XPM (X Pixmap) library
+XPMROOT  := $(X11ROOT)
+
+# TCL (version 7.6 or later).
+TCLROOT  := /usr
+TCLLIB   += -ldl
+
+# PGPLOT (FORTRAN plotting package).
+PGPLOTROOT := /usr
+
+# FFTW Highly optimized FFT routines
+FFTWROOT := /usr
+FFTWLIB := -lfftw -lrfftw
+
+CFITSIOROOT := /usr
+
+RPFITSROOT := /usr/local
+
+#-----------------------------------------------------------------------------
+# Compilation and linkage variables
+#-----------------------------------------------------------------------------
+# The C++ compiler.
+C++ROOT := /usr
+
+# Preprocessor flags for system debug/optimized compilations.
+CPPSTD   += -DAIPS_LINUX -DAIPS_LITTLE_ENDIAN -DAIPS_STDLIB -DAIPS_NO_LEA_MALLOC  -D_GLIBCPP_DEPRECATED -DAIPS_AUTO_STL
+
+# C compiler flags for system debug/optimized compilations.
+CC      := $(C++ROOT)/bin/gcc
+CSTD    += -DHAVE_LINUX_GLIBC -Wall
+COPTLVL := -O2 -fno-strength-reduce
+
+# C++ compiler flags for system debug/optimized compilations.
+C++       := $(C++ROOT)/bin/g++ -Wno-deprecated
+C++STD    += -pipe -Wall -Wno-non-template-friend -Woverloaded-virtual -Wcast-align -Wno-comment -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE
+
+
+# FORTRAN compiler - using gcc (g77)
+F77ROOT  := $(C++ROOT)
+F77LIB := -lg2c
+FC       := $(F77ROOT)/bin/g77
+FCOPTLVL := -O2
+FCOPT    += -fPIC
+
+# Link flags for system debug/optimized compilations; these are passed to
+# the C++ compiler.
+LDSTD := -Xlinker -rpath -Xlinker $(AIPSARCH)/lib:$(C++ROOT)/lib:$(MotifLIBD):$(X11LIBD)
+
+# Control table for building AIPS++ libraries.  Four keywords are recognized:
+#    defeat:  The library will not be built (and if an old version exists it
+#             will be deleted).
+#    static:  The static library is built and is the one linked to.
+#    shatic:  The static and shared libraries are built but the static library
+#             is the one linked to.
+#    shared:  The static and shared libraries are built and the shared library
+#             is the one linked to.
+#
+#                    C++            C          FORTRAN
+#                 dbg    opt    dbg    opt    dbg    opt
+#               ------------- ------------- -------------
+LIBatnf      := defeat static defeat defeat defeat defeat
+
+# What consortium packages to build (atnf bima hia nfra nral nrao tifr).
+CONSORTIA += atnf
+
+# Absolute pathname of perl-5 executable
+PERL5 := /usr/bin/perl
+
+# Unset this to defeat document compilation.
+DOCSYS :=
+
+# Unset this to defeat document extraction from C++ sources.
+DOCEXTR :=
Index: tags/Release2.1.0b/external/casa_asap/linux/UNKNOWN_SITE/makedefs.64
===================================================================
--- tags/Release2.1.0b/external/casa_asap/linux/UNKNOWN_SITE/makedefs.64	(revision 1196)
+++ tags/Release2.1.0b/external/casa_asap/linux/UNKNOWN_SITE/makedefs.64	(revision 1196)
@@ -0,0 +1,152 @@
+#-----------------------------------------------------------------------------
+# makedefs: Installation-specific definitions used by AIPS++ makefiles
+#-----------------------------------------------------------------------------
+#
+#   Copyright (C) 1997,1998,1999,2000,2001,2003
+#   Associated Universities, Inc. Washington DC, USA.
+#
+#   This program is free software; you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation; either version 2 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program; if not, write to the Free Software
+#   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+#   Correspondence concerning AIPS++ should be addressed as follows:
+#          Internet email: aips2-request@nrao.edu.
+#          Postal address: AIPS++ Project Office
+#                          National Radio Astronomy Observatory
+#                          520 Edgemont Road
+#                          Charlottesville, VA 22903-2475 USA
+#
+#-----------------------------------------------------------------------------
+# This GNU makefile is read in by the makedefs file in the directory above
+# this.  That "default makedefs" file defines default values of each and
+# every variable required by the AIPS++ makefiles.  Those default values are
+# overridden where necessary by redefining them in this file.
+#
+# Installation instructions:
+#
+#    A template version of this file was copied from the "install/linux_gnu"
+#    directory into the site-specific AIPS++ system directory for this
+#    architecture (this directory) by the 'configure' script as part of the
+#    AIPS++ installation.
+#
+#    While default definitions are made in the default makedefs file in the
+#    directory above this, certain platform-specific redefinitions may have
+#    been included here to help make the installation easier.
+#
+#    You must carefully check all of the definitions made in the default
+#    makedefs file (as well as the redefinitions made here) and if necessary,
+#    correct them by making an overriding definition (or correction) here.
+#    However,
+#
+#                  DO NOT MODIFY THE DEFAULT MAKEDEFS FILE -
+#
+#                         MODIFY THIS FILE INSTEAD
+#
+#    Certain of the defaults in the default makedefs file are expressed in
+#    terms of another default using "recursively expanded" variables (defined
+#    with '=') rather than "simply expanded" (defined with ':=').  For
+#    example the TeX variables are defined in terms of "TEXROOT".  This may
+#    allow this makedefs file to redefine a whole set of variables, for
+#    example all of the TeX variables, simply by redefining one, TEXROOT.
+#    However,
+#
+#           ALWAYS USE "SIMPLY EXPANDED" VARIABLES WITHIN THIS FILE
+#
+#    That is, always use ":=" to make the assignments.
+#
+# Original: 1996/08/30 by Mark Calabretta, ATNF
+# $Id: makedefs.template,v 19.3 2006/05/25 15:49:39 wyoung Exp $
+#=============================================================================
+
+# General external libraries. 
+#---------------------------- 
+
+#RPFITS
+RPFITSROOT := /usr/local
+
+CFITSIOROOT := /usr
+
+# X11.
+X11ROOT := /usr/X11R6
+
+# Motif (lesstif is fine).
+MotifROOT := $(X11ROOT)/LessTif/Motif2.1
+
+# XPM (X Pixmap) library
+XPMROOT  := $(X11ROOT)
+
+# TCL (version 7.6 or later).
+TCLROOT  := /usr
+TCLLIB   += -ldl
+
+# PGPLOT (FORTRAN plotting package).
+PGPLOTROOT := /usr
+
+# FFTW Highly optimized FFT routines
+FFTWROOT := /usr
+FFTWLIB := -lfftw -lrfftw
+
+#-----------------------------------------------------------------------------
+# Compilation and linkage variables
+#-----------------------------------------------------------------------------
+# The C++ compiler.
+C++ROOT := /usr
+
+# Preprocessor flags for system debug/optimized compilations.
+CPPSTD   += -DAIPS_LINUX -DAIPS_LITTLE_ENDIAN -DAIPS_STDLIB -DAIPS_NO_LEA_MALLOC  -D_GLIBCPP_DEPRECATED -D__x86_64__ -DAIPS_64B -DAIPS_AUTO_STL
+
+# C compiler flags for system debug/optimized compilations.
+CC      := $(C++ROOT)/bin/gcc
+CSTD    += -DHAVE_LINUX_GLIBC -Wall -fPIC
+COPTLVL := -O2 -fno-strength-reduce -funroll-all-loops -ftracer
+
+# C++ compiler flags for system debug/optimized compilations.
+C++       := $(C++ROOT)/bin/g++ -Wno-deprecated
+C++STD    += -pipe -Wall -Wno-non-template-friend -Woverloaded-virtual -Wcast-align -Wno-comment -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D__x86_64__
+
+# FORTRAN compiler - using gcc (gfortran)
+F77ROOT  := $(C++ROOT)
+F77LIB := -lg2c
+FC       := $(F77ROOT)/bin/g77
+FCOPTLVL := -O2 -funroll-all-loops -fPIC
+FCOPT    += -fPIC
+
+# Link flags for system debug/optimized compilations; these are passed to
+# the C++ compiler.
+LDSTD := -Xlinker -rpath -Xlinker $(AIPSARCH)/lib:$(C++ROOT)/lib:$(MotifLIBD):$(X11LIBD)
+
+# Control table for building AIPS++ libraries.  Four keywords are recognized:
+#    defeat:  The library will not be built (and if an old version exists it
+#             will be deleted).
+#    static:  The static library is built and is the one linked to.
+#    shatic:  The static and shared libraries are built but the static library
+#             is the one linked to.
+#    shared:  The static and shared libraries are built and the shared library
+#             is the one linked to.
+#
+#                    C++            C          FORTRAN
+#                 dbg    opt    dbg    opt    dbg    opt
+#               ------------- ------------- -------------
+LIBatnf      := defeat static defeat defeat defeat defeat
+
+# What consortium packages to build (atnf bima hia nfra nral nrao tifr).
+CONSORTIA += atnf
+
+# Absolute pathname of perl-5 executable
+PERL5 := /usr/bin/perl
+
+# Unset this to defeat document compilation.
+DOCSYS :=
+
+# Unset this to defeat document extraction from C++ sources.
+DOCEXTR :=
Index: tags/Release2.1.0b/external/casa_asap/linux/UNKNOWN_SITE/makedefs.darwin
===================================================================
--- tags/Release2.1.0b/external/casa_asap/linux/UNKNOWN_SITE/makedefs.darwin	(revision 1196)
+++ tags/Release2.1.0b/external/casa_asap/linux/UNKNOWN_SITE/makedefs.darwin	(revision 1196)
@@ -0,0 +1,191 @@
+##############################################################################
+##  Compilers
+##############################################################################
+
+C++ROOT := /usr
+C++     := $(C++ROOT)/bin/g++ -Wno-deprecated -Wno-long-double
+C++OPTLVL :=  -O2   -falign-loops=32
+C++STD += -fexceptions -pipe -Wall -Wno-non-template-friend -Woverloaded-virtual -Wcast-align -Wno-comment
+
+##  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --
+
+CC       := $(C++ROOT)/bin/gcc
+CSTD	:= -DAIPS_DARWIN -Wall -fexceptions
+COPTLVL :=  -O2 -fno-strength-reduce -falign-loops=32
+
+##  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --
+
+F77ROOT  := /usr/local
+FC       := $(F77ROOT)/bin/g77
+FCOPTLVL := -O2 -falign-loops=32
+FCOPT    += -fPIC
+F77LIB := -lg2c
+
+##############################################################################
+##  General external libraries. 
+##############################################################################
+
+PGPLOTROOT := /opt/local
+
+TCLROOT := /opt/local
+TCLLIB  := -ltk -ltcl -ldl
+
+X11ROOT := /usr/X11R6
+
+MotifROOT := /usr/X11R6
+
+XPMROOT  := $(X11ROOT)
+
+LAPACKLIBD := 
+LAPACKLIB := -framework vecLib
+
+# Platform-specific libraries - always added.
+ARCHLIBS := -lm
+
+##############################################################################
+##  External libraries required by particular AIPS++ packages.
+##############################################################################
+
+# RPFITS (required by ATNF).
+RPFITSROOT := /usr/local
+RPFITSDEFS :=
+RPFITSINCD  = $(RPFITSROOT)/include
+RPFITSLIBD  = $(RPFITSROOT)/lib
+RPFITSLIB  := -lrpfits
+
+CFITSIOROOT := /usr/local
+CFITSIODEFS :=
+CFITSIOINCD  = $(CFITSIOROOT)/include/cfitsio
+CFITSIOLIBD  = $(CFITSIOROOT)/lib
+CFITSIOLIB  := -lcfitsio
+
+WCSLIBLIB  := $(WCSLIBLIBD)/libwcs.a
+MIRLIBLIB  := $(MIRLIBLIBD)/libmir.a
+
+##############################################################################
+##  Compilation and linkage variables
+##############################################################################
+
+EXT_GLISH_DIR := /opt/local
+GLISHLOLIB  :=
+
+# Preprocessor flags for system debug/optimized compilations.
+CPPSTD   := -DAIPS_STDLIB -DAIPS_DARWIN -DAIPS_BIG_ENDIAN -D_GLIBCPP_DEPRECATED -DAIPS_NO_LEA_MALLOC -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -DAIPS_AUTO_STL
+CPPSTD   += -DNEED_FORTRAN_UNDERSCORES=1
+
+# Use do-it-yourself template instantiation.
+TEMPLATES := diy
+
+# Link flags for system debug/optimized compilations; these are passed to
+# the C++ compiler.
+LDDBG := -Xlinker -multiply_defined -Xlinker suppress -Xlinker -bind_at_load \
+	-L/opt/local/lib
+LDOPT := $(LDDBG) 
+
+RANLIB := /usr/bin/ranlib
+
+SFXSHAR := dylib
+LDSOPTS := -dynamiclib -single_module
+
+PERL4 := /usr/bin/perl
+PERL5 := /usr/bin/perl
+
+#                      C++            C          FORTRAN
+#                   dbg    opt    dbg    opt    dbg    opt
+#                 ------------- ------------- -------------
+LIBcasa        := defeat static defeat defeat defeat defeat
+LIBscimath     := defeat static defeat defeat defeat static
+LIBtables      := defeat static defeat defeat defeat defeat
+LIBmeasures    := defeat static defeat defeat defeat static
+LIBfits        := defeat static defeat defeat defeat defeat
+LIBgraphics    := defeat static defeat defeat defeat defeat
+LIBlattices    := defeat static defeat defeat defeat defeat
+LIBcoordinates := defeat static defeat defeat defeat defeat
+LIBcomponents  := defeat static defeat defeat defeat defeat
+LIBimages      := defeat static defeat defeat defeat defeat
+LIBms          := defeat static defeat defeat defeat defeat
+LIBmsvis       := defeat static defeat defeat defeat defeat
+LIBcalibration := defeat static defeat defeat defeat defeat
+LIBionosphere  := defeat static defeat defeat defeat static
+LIBflagging    := defeat static defeat defeat defeat defeat
+LIBdish        := defeat static defeat defeat defeat defeat
+LIBsimulators  := defeat static defeat defeat defeat defeat
+LIBsynthesis   := defeat static defeat defeat defeat static
+LIBtasking     := defeat static defeat defeat defeat defeat
+LIBtrialdisplay  := defeat static defeat defeat defeat defeat
+LIBnrao          := defeat static defeat defeat defeat defeat
+LIBnpoi          := defeat defeat defeat defeat defeat defeat
+LIBalma          := defeat static defeat defeat defeat defeat
+LIBatnf          := defeat static defeat defeat defeat defeat
+LIBnfra          := defeat static defeat defeat defeat defeat
+
+
+# TeX, dvips, and MetaFont paths.
+#--------------------------------
+TEXROOT    := /usr/lib/texmf/texmf
+TEXFONTS   := $(TEXROOT)/fonts/tfm//
+TEXFORMATS := $(TEXROOT)/web2c
+TEXINPUTS  := $(TEXROOT)/tex//
+TEXPOOL    := $(TEXROOT)/web2c
+TEXPKS     := $(TEXROOT)/fonts/pk//
+MFBASES    := $(TEXROOT)/web2c
+MFINPUTS   := $(TEXROOT)/metafont//:$(TEXROOT)/fonts/source//
+MFPOOL     := $(TEXROOT)/web2c
+LATEX2HTML := /usr/local/bin/latex2html -init_file $(AIPSROOT)/code/install/docutils/latex2html-init
+
+# Set these variables to get local icons in the html files generated by
+# latex2html
+ICONSERVER := http://www.nrao.edu/icons/latex2html
+ICONS2LOCAL := $(AIPSARCH)/bin/redoicons.sh              
+
+# Variables which tell the makefiles what to do and how to do it
+#-----------------------------------------------------------------------------
+# Command for benchmarking the rebuild (usually 'adate -l').
+TIMER := adate -l
+
+# Unset this to defeat document compilation.
+DOCSYS :=
+
+# Unset this to defeat document extraction from C++ sources.
+DOCEXTR :=
+
+# What consortium packages to build (atnf bima hia nfra nral nrao tifr).
+CONSORTIA := atnf
+
+#-----------------------------------------------------------------------------
+# The remaining definitions are programmer variables for code development.
+# These are not required for production-line systems.
+#-----------------------------------------------------------------------------
+ifeq "$(MAKEMODE)" "programmer"
+   # Alternate definitions for CPPFLAGS.
+   ifeq "$(CPPFLAGS)" "alt1"
+      override CPPFLAGS := $(CPPOPT) $(EXTRA_CPPFLAGS)
+   endif
+
+   # C++ compiler flags for programmer compilations.
+   ifdef OPT
+      C++FLAGS := $(C++OPT) -Wall
+   else
+      C++FLAGS := $(C++DBG) -Wall
+   endif
+
+   # Where to put binaries for programmer compilations.
+   ifdef OPT
+      PGMRLIBD := $(wildcard $(PGMRARCH)/lib)
+      PGMRBIND := $(wildcard $(PGMRARCH)/bin)
+   else
+      PGMRLIBD := $(wildcard $(PGMRARCH)/libdbg)
+      PGMRBIND := $(wildcard $(PGMRARCH)/bindbg)
+   endif
+
+   # Alternate definitions for C++FLAGS.
+   ifeq "$(C++FLAGS)" "alt1"
+      override CPPFLAGS := $(CPPOPT) $(EXTRA_CPPFLAGS)
+      PGMRBIND := $(wildcard $(PGMRARCH)/bin)
+      PGMRLIBD := $(wildcard $(PGMRARCH)/lib)
+      override C++FLAGS := $(C++OPT) -Wall
+   endif
+
+   # Extra compiler flags.
+   C++FLAGS += $(EXTRA_C++FLAGS)
+endif
Index: tags/Release2.1.0b/monitor/README
===================================================================
--- tags/Release2.1.0b/monitor/README	(revision 1196)
+++ tags/Release2.1.0b/monitor/README	(revision 1196)
@@ -0,0 +1,13 @@
+ASAP Online monitor
+-------------------
+
+The monitor is a python cgi script using TAL fro html templating.
+
+The default locations are:
+
+/var/www/asapmon - for templates
+/opt/lib/cgi-bin/asapmon - for the cgi scripts
+
+The sys environment has to be configured by editing "asapconfig.py".
+The observatory specific file in terms of data is in "obsconfig.py".
+
Index: tags/Release2.1.0b/monitor/cgi-bin/asapconfig.py
===================================================================
--- tags/Release2.1.0b/monitor/cgi-bin/asapconfig.py	(revision 1196)
+++ tags/Release2.1.0b/monitor/cgi-bin/asapconfig.py	(revision 1196)
@@ -0,0 +1,20 @@
+#asapconfig.py
+
+observatory = {'rpfpath': [],
+               'name': 'observatory',
+               'lines' : {} }
+
+import os,sys
+# This is where asap lives
+sys.path.insert(2,'/opt/lib/python2.3/site-packages')
+os.environ["AIPSPATH"]="/opt/share/asap linux_gnu somewhere localhost"
+
+#overwrite /usr/local/... as default
+sys.path.insert(2,'/usr/lib/python2.3/site-packages')
+
+# This is needed for plotting with matplotlib
+# where matplotlib data is located
+os.environ["MATPLOTLIBDATA"]="/opt/share/matplotlib"
+# where matplotlib puts it temporary font files
+# this location can also have a custom .matplotlibrc
+os.environ["HOME"]="/var/www/asapmon/tmp"
Index: tags/Release2.1.0b/monitor/cgi-bin/asapmon.py
===================================================================
--- tags/Release2.1.0b/monitor/cgi-bin/asapmon.py	(revision 1196)
+++ tags/Release2.1.0b/monitor/cgi-bin/asapmon.py	(revision 1196)
@@ -0,0 +1,271 @@
+#!/usr/bin/python
+import sys,os
+import cgi
+#cgi debug
+import cgitb; cgitb.enable()
+
+from simpletal import simpleTAL, simpleTALES
+
+#absolute home
+abspath= "/var/www/asapmon/"
+cgiloc = "/cgi-bin/asapmon/"
+tmppath = abspath+"tmp/"
+htmlloc = "/asapmon/"
+tmploc = "/asapmon/tmp/"
+
+from obsconfig import *
+
+# a redirection object for stdout/stderr
+class WritableObject:
+    def __init__(self):
+        self.content = []
+    def write(self, string):
+        self.content.append(string)
+
+logsink = WritableObject()
+logsink2 = WritableObject()
+sys.stdout =logsink
+sys.stderr = logsink2
+import asap
+sys.stdout = sys.__stdout__
+sys.stderr = sys.__stderr__
+
+def resetstd():
+    sys.stdout = sys.__stdout__
+    sys.stderr = sys.__stderr__
+
+
+class myForm:
+    def __init__(self):
+        self.fields = {}
+        self.form = cgi.FieldStorage()
+        self.context = simpleTALES.Context(allowPythonPath=1)
+        self.logsink = WritableObject()
+
+    def decodePath(self):
+        pi = self.fields['cdir']
+        fi = self.fields['cfile']
+        p = observatory['rpfpath'][pi]
+        from filelist import FileList
+        fl = FileList(pi)
+        if  fl.error:
+            return None
+        out = []
+        for i in fi:
+            out.append(p+"/"+fl.files[i])
+        return out
+
+    def decodeWindow(self,window):
+        if not len(window.strip()): return None,None
+        x = window.split(",")
+        return [float(x[0].strip()),float(x[1].strip())]
+
+    def decodeWindows(self,window):
+        import re
+        p = re.compile("(\\[*\d+\\.*\d*,\d+\\.*\d*\\]*)")
+        r = re.compile("[\\[\\]]")
+        return [self.decodeWindow(re.sub(r,'',s)) for s in re.findall(p,window)]
+
+    def setDefaultFields(self):
+        self.fields['directories'] = observatory['rpfpath']
+        self.fields['cdir'] = len(self.fields['directories'])-1
+        from filelist import FileList
+        files = []
+        fl = FileList(len(observatory['rpfpath'])-1)
+        if not fl.error:
+            self.fields['files'] = fl.files
+        self.fields['cfile'] = len(fl.files)-1
+        self.fields['restfreqs'] = observatory['lines'].keys()
+        self.fields['border'] = range(10)
+        self.fields['imagename'] = ""
+        self.fields['cunit'] = 0
+        self.fields['units'] = ["channel","km/s","GHz"]
+        self.fields['baseline'] = 0
+        self.fields['cpolyorder'] = 0
+        self.fields['quotient'] = 0
+        self.fields['average'] = 0
+        self.fields['doppler'] = "RADIO"
+        self.fields['frame'] = "LSRK"
+        self.fields['restn'] = [11,1]
+        self.fields['stokes'] = 0
+        self.fields['summary'] = ""
+        self.fields['bin'] = 0
+        self.fields['brangewindow'] = ""
+        self.fields['nif'] = []
+        self.fields['sourcenames'] = []
+        self.fields['csource'] = ""
+
+
+    def getFormFields(self):
+        self.fields['cunit'] = int(self.form.getfirst("unit",0))
+        self.fields['frame'] = self.form.getfirst("frame","TOPO")
+        self.fields['doppler'] = self.form.getfirst("doppler","RADIO")
+        self.fields['restn'] = []
+
+        self.fields['plotwindow'] = self.form.getfirst("plotwindow","")
+        self.fields['baseline'] = int(self.form.has_key("baseline"))
+        self.fields['cpolyorder'] = int(self.form.getfirst("polyorder",0))
+        self.fields['quotient'] = int(self.form.has_key("quotient"))
+        self.fields['doppler'] = self.form.getfirst("doppler","RADIO")
+        self.fields['frame'] = self.form.getfirst("frame","LSRK")
+        self.fields['cdir'] = int(self.form.getfirst("dlist",None))
+        self.fields['cfile'] = [int(k) for k in self.form.getlist("list")]
+        self.fields['average'] = int(self.form.has_key("average"))
+        self.fields['stokes'] = int(self.form.has_key("stokes"))
+        self.fields['bin'] = int(self.form.has_key("bin"))
+        self.fields['debug'] = ""#self.fields['restn']
+        self.fields['csource'] = self.form.getfirst("csource","")
+
+    def getRest(self):
+        alllines = observatory['lines'].values()
+        lines = []
+        for i in self.fields['restn']:
+            lines.append(alllines[i])
+        return lines
+
+    def plotForm(self):
+        self.getFormFields()
+        # decode file location
+        from filelist import FileList
+        fl = FileList(self.fields['cdir'])
+        self.fields['files'] = fl.files
+        files = self.decodePath()
+        # catch all stdout/err
+        sys.stdout = logsink
+        sys.stderr = logsink2
+        try:
+            s = asap.scantable(files)
+            outscans = None
+            self.fields['nif'] = range(s.nif())
+            for i in self.fields['nif']:
+                name = "rest%d" % i
+                self.fields['restn'].append(int(self.form.getfirst(name,0)))
+            restfs = self.getRest()
+
+            # source name selection
+            import re
+            srcnames = s.get_sourcename()
+            for i in srcnames:
+                # only add the names once
+                i = not i in self.fields['sourcenames'] and i
+                if i:
+                    # filter off scans
+                    i = not re.search(re.compile("_[R,e,w]$"),i) and i
+                    if i:
+                        self.fields['sourcenames'].append(i)
+            # form quotient
+            if self.form.has_key("quotient"):
+                s = s.auto_quotient()
+            # get source by name
+            cs = self.fields['csource']
+            if len(cs) > 0:
+                if cs in self.fields['sourcenames']:
+                    ss = s.get_scan(self.fields['csource'])
+                    if isinstance(ss,asap.scantable):
+                        s = ss
+                    del ss
+            else:
+                # get only the last source in the table if not averaging
+                s = s.get_scan(self.fields['sourcenames'][-1])
+                #self.fields['debug'] = "DEBUG"
+                self.fields['csource'] = s.get_sourcename()[-1]
+            if self.fields['cunit'] == 1:
+                srest = s._getrestfreqs()
+                if isinstance(srest,tuple) and len(srest) != s.nif():
+                    s.set_restfreqs(restfs,unit="GHz")
+            s.set_unit(self.fields['units'][self.fields['cunit']])
+            s.set_freqframe(self.form.getfirst("frame","LSRK"))
+            s.set_doppler(self.form.getfirst("doppler","RADIO"))
+
+            # baseline
+            if self.form.has_key('baseline'):
+                order = self.fields['cpolyorder']
+                brstr = self.form.getfirst('brangewindow','').strip()
+                # auto baseline or user window
+                if brstr:
+                    self.fields['brangewindow'] = brstr
+                    brange = self.decodeWindows(brstr)
+                    if len(brange):
+                        self.fields['brangewindow'] = brstr
+                        m = s.create_mask(brange)
+                        s.poly_baseline(mask=m,order=order)
+                else:
+                    s.auto_poly_baseline(order=order)
+            outscans = None
+
+            if self.fields['average']:
+                outscans = s.average_time(weight='tsys')
+            else:
+                outscans = s
+            del s
+
+            if self.fields['bin']:
+                outscans.bin()
+
+            self.fields['summary'] = str(outscans)
+            asap.rcParams['plotter.decimate'] = True
+            asap.rcParams['plotter.ganged'] = False
+            from matplotlib import rcParams as rcp
+            rcp['xtick.labelsize'] = 8
+            rcp['ytick.labelsize'] = 8
+            rcp['axes.labelsize'] = 8
+            rcp['axes.titlesize'] = 12
+            rcp['figure.subplot.wspace'] = 0.3
+            rcp['figure.subplot.hspace'] = 0.3
+            del asap.plotter
+            # plotter without GUI
+            asap.plotter = asap.asapplotter(False)
+            if outscans.nif() > 1:
+                asap.plotter.set_mode("p","i")
+            else:
+                if outscans.npol() > 2:
+                    asap.plotter.set_mode("t","p")
+                else:
+                    asap.plotter.set_mode("p","t")
+            asap.plotter.plot(outscans)
+            if self.fields['stokes']:
+                pols = "I"
+                if outscans.npol() > 2:
+                    pols += " Q U V"
+                sel = asap.selector()
+                sel.set_polarisations(pols)
+                asap.plotter.set_selection(sel)
+            x0,x1 = self.decodeWindow(self.fields['plotwindow'])
+            asap.plotter.set_range(x0,x1)
+            imname = tmppath+"plot.png"
+            asap.plotter.save(imname,dpi=96)
+            self.fields['imagename'] = tmploc+"plot.png"
+        except RuntimeError,e:
+            self.fields['debug'] = e
+            return
+
+
+    def buildContext (self, title):
+        self.context.addGlobal("fields", self.fields)
+        self.context.addGlobal("title", title)
+
+    def expandTemplate (self, templateName):
+        sys.stdout.write ("Content-Type: text/html\n")
+        sys.stdout.write ("\n")
+        # Expand the template and print it out
+        templateFile = open(templateName, 'r')
+        template = simpleTAL.compileHTMLTemplate(templateFile)
+        templateFile.close()
+        # Expand the template as HTML using this context
+        template.expand(self.context, sys.stdout)
+        sys.exit(0)
+
+    def main(self):
+        self.setDefaultFields()
+        title = "ASAP %s Online Monitor" % (observatory['name'])
+        tmplname = abspath+"asapmon.html.template"
+        if ( self.form.has_key("plot")):
+            self.plotForm()
+        self.buildContext(title)
+        resetstd()
+        #os.system('rm -rf /var/www/asapmon/tmp/.matplotlib')
+        self.expandTemplate(tmplname)
+
+
+f = myForm()
+f.main()
Index: tags/Release2.1.0b/monitor/cgi-bin/filelist.py
===================================================================
--- tags/Release2.1.0b/monitor/cgi-bin/filelist.py	(revision 1196)
+++ tags/Release2.1.0b/monitor/cgi-bin/filelist.py	(revision 1196)
@@ -0,0 +1,48 @@
+#!/usr/bin/python
+import os
+from obsconfig import observatory
+
+class FileList:
+
+    def __init__(self, loc=None):
+        self.message ="""Content-Type: text/xml
+
+<?xml version="1.0" encoding="ISO-8859-1"?>"""
+
+        self.error = None
+        if loc is None:
+            loc = observatory['rpfpath'][0]
+        else:
+            loc = int(loc)
+            if loc< 0 or loc >=len(observatory['rpfpath']):
+                 self.error = "Invalid Path"
+                 return
+            else:
+                loc = observatory['rpfpath'][loc]
+        if os.path.exists(loc) and os.path.isdir(loc):
+            self.files = filter(lambda x: x.lower().endswith("rpf"), os.listdir(loc))
+            if len(self.files) == 0:
+                self.error = "No rpfits files found"
+        else:
+            self.error = "Invalid Path"
+
+
+    def __str__(self):
+        if self.error:
+            self.message += "<Error>\n"
+            self.message += self.error
+            self.message += "</Error>\n"
+        else:
+            self.message += "<Listing>\n"
+            for s in self.files:
+                self.message += "<File>" + s + "</File>\n"
+            self.message += "</Listing>\n"
+        return self.message
+if __name__ == "__main__":
+    import cgi
+    form = cgi.FieldStorage()
+    if form.has_key('path'):
+        pathindex = form.getfirst("path",0)
+        print FileList(pathindex)
+    else:
+        print FileList()
Index: tags/Release2.1.0b/monitor/cgi-bin/obsconfig.py
===================================================================
--- tags/Release2.1.0b/monitor/cgi-bin/obsconfig.py	(revision 1196)
+++ tags/Release2.1.0b/monitor/cgi-bin/obsconfig.py	(revision 1196)
@@ -0,0 +1,25 @@
+from asapconfig import *
+
+# the name of the observatory
+observatory['name'] = 'Mopra'
+
+# Append observing directories
+observatory['rpfpath'].append("/u/mar637/brage/singledish/data")
+observatory['rpfpath'].append("/u/mar637/brage/singledish/data/mopra200505")
+observatory['rpfpath'].append("/u/mar637/brage/singledish/data/mopra/phoenix")
+
+# Restfrequencies in GHz
+observatory['lines'] = {
+    'SiO (85640.456 MHz)': 85.640456,
+    'SiO (86243.442 MHz)': 86.243442,
+    'H13CO+ (86754.294 MHz)' : 86754.294,
+    'SiO (86846.998 MHz)' : 86.846998,
+    'HCN (88631.847 MHz)' : 88.631847,
+    'HCO+ (89188.518 MHz)' : 89.188518,
+    'HNC (90663.574 MHz)' : 90.663574,
+    'N2H+ (93173.809 MHz)' : 93.173809,
+    'CS (97980.968 MHz)' : 97.980968,
+    '12C18O(1-0) (109782.182 MHz)' : 109.782182,
+    '13CO(1-0) (110201.37 MHz)' : 110.20137,
+    '12CO(1-0) (115271.203 MHz)' : 115.271203
+}
Index: tags/Release2.1.0b/monitor/htdocs/asapmon.css
===================================================================
--- tags/Release2.1.0b/monitor/htdocs/asapmon.css	(revision 1196)
+++ tags/Release2.1.0b/monitor/htdocs/asapmon.css	(revision 1196)
@@ -0,0 +1,114 @@
+
+body {
+    font-family: verdana,sans-serif;
+    font-size: 8pt;
+}
+
+input, select {
+    font-size: 8pt;
+}
+
+input.plot {
+    border-top-width: 1px;
+    border-right-width: 1px;
+    border-bottom-width: 1px;
+    border-left-width: 1px;
+    border-top-style: solid;
+    border-right-style: solid;
+    border-bottom-style: solid;
+    border-left-style: solid;
+    border-top-color: gray;
+    border-right-color: gray;
+    border-bottom-color: gray;
+    border-left-color: gray;
+}
+
+abbr, acronym {
+    text-decoration: none;
+    border: 0;
+    cursor: help;
+}
+
+#sourcelist,#filelist {
+    vertical-align: top;
+}
+
+tr,td {
+    padding:0;
+}
+
+legend {
+        color: white;
+        border: solid #8F8FB3;
+        border-width: 1px;
+        padding: 0.3em;
+        margin-bottom: 0.5em;
+        background-color: #ADADD9;
+        font-weight: bold;
+        letter-spacing: 0.2em;
+        font-size: smaller;
+}
+
+fieldset {
+    border: 0px solid;
+    border-top-width: 2px;
+    border-color: rgb(230,230,230);
+    border-top-color: #ABB8C7;
+    padding: 0.2em 0 0.5em 0.5em;
+    background-color: #F4F4F4;
+    width: auto;
+    margin-right: 0.5em;
+}
+
+fieldset.velocity {
+   //height: 45px;
+}
+
+.floatleft {
+  height: 22px;
+  float:left;
+}
+
+
+.labeled {
+    padding-left:0.5em;
+    padding-right:0.5em;
+    margin-left: 2px;
+    margin-right: 2px;
+    border: solid 1px #a4a4a4;
+}
+
+#plotrange {
+    //width:250px;
+}
+
+#prangeunit,#brangeunit {
+    color: #888888;
+}
+
+#velunit {
+//width:195px;
+padding-left:0px;
+float:left;
+}
+#velframe {
+//width:145px;
+padding-left:5px;
+float:left;
+display: none;
+
+}
+#veldoppler {
+//width:135px;
+padding-left:5px;
+float:left;
+display: none;
+}
+
+#velrest {
+//width:130px;
+padding-left:5px;
+float:left;
+display: none;
+}
+
Index: tags/Release2.1.0b/monitor/htdocs/asapmon.html.template
===================================================================
--- tags/Release2.1.0b/monitor/htdocs/asapmon.html.template	(revision 1196)
+++ tags/Release2.1.0b/monitor/htdocs/asapmon.html.template	(revision 1196)
@@ -0,0 +1,231 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+    <head>
+        <title tal:content="title">ASAP Online Monitor</title>
+        <link rel="stylesheet" href="/asaptest/fields.css" type="text/css">
+        <script type="text/javascript" src="/asapmon/prototype.js"></script>
+        <script type="text/javascript" src="/asapmon/asapmon.js"></script>
+    </head>
+
+<body onload="javascript:repopulate();">
+    <h1 tal:content="title">ASAP Online Monitor</h1>
+    <form name="hidform" id="hidform">
+        <input type="hidden" id="cunit" tal:attributes="value python:fields['units'][fields['cunit']]"/>
+        <input type="hidden" id="cdoppler" tal:attributes="value fields/doppler"/>
+        <input type="hidden" id="cframe" tal:attributes="value fields/frame"/>
+        <input type="hidden" id="cfilesel" tal:attributes="value fields/cfile"/>
+        <input type="hidden" id="nif" tal:attributes="value fields/nif"/>
+        <input type="hidden"
+            tal:repeat="i fields/restn"
+            tal:attributes="id string: cif${repeat/i/index};value i"
+        />
+        <input type="hidden" id="debug" tal:attributes="value fields/debug"/>
+        <input type="hidden" id="sum" tal:attributes="value fields/summary"/>
+        <input type="hidden" id="csource" tal:attributes="value fields/csource"/>
+    </form>
+    <form name="specplot" action="/cgi-bin/asapmon/asapmon.py">
+        <fieldset>
+        <legend>File selection</legend>
+            Directory:
+            <select id="directory" name="dlist" onclick="invalidateFiles();">
+                <option
+                    tal:repeat="entry fields/directories"
+                    tal:content="entry"
+                    tal:attributes=" value repeat/entry/index;selected python:int(fields['cdir'])==int(repeat['entry'].getIndex()) and 'selected' or None"
+                >somewhere over the rainbow</option>
+            </select>
+            <input class="plot" style="display: none" type="button" name="flist" onClick="insertFields();" value="Update --&gt;"></input>
+            <label id="filelbl">
+                <abbr title="Select one or more file(s).">
+                    File:
+                </abbr>
+            <select id="filelist" name="list" multiple size="5" onclick="invalidateSources();">
+                <option
+                    tal:repeat="entry fields/files"
+                    tal:content="entry"
+                    tal:attributes="value repeat/entry/index"
+                >
+                </option>
+            </select>
+            </label>
+            <label id="sourcelbl">
+                <abbr title="Select a source if your file contains multiple sources.">
+                    Source:
+                </abbr>
+            <select id="sourcelist" name="csource" size="5">
+                <option
+                    tal:repeat="entry fields/sourcenames"
+                    tal:content="entry"
+                    tal:attributes="value entry"
+                >
+                </option>
+            </select>
+            </label>
+        </fieldset>
+        <p>
+        <fieldset>
+        <legend>Spectral line settings</legend>
+            <div id="velunit">
+            <fieldset name="unitset" class="velocity">
+                <legend>Unit</legend>
+                <span tal:repeat="u fields/units">
+                    <input type="radio" name="unit"
+                        tal:attributes="value repeat/u/index;
+                                        checked python: int(fields['cunit'])==int(repeat['u'].getIndex()) and 'checked' or None;onclick string:unitswitch('${u}');
+                                        id string: unit${repeat/u/index}"
+                    >
+                    <label tal:content="u"></label>
+
+               </span>
+            </fieldset>
+            </div>
+            <div id="velframe">
+            <fieldset class="velocity">
+                <legend>Frame</legend>
+                <label>
+                    <input type="radio" name="frame" id="frame0" value="LSRK">
+                    LSRK
+                </label>
+                <label>
+                    <input type="radio" name="frame" id="frame1" value="BARY">
+                    BARY
+                </label>
+            </fieldset>
+            </div>
+            <div id="veldoppler">
+            <fieldset class="velocity">
+                <legend>Doppler</legend>
+                <label>
+                    <input type="radio" name="doppler" id="doppler0" value="RADIO">
+                    RADIO
+                </label>
+                <label>
+                    <input type="radio" name="doppler" id="doppler1" value="OPTICAL">
+                    OPTICAL
+                </label>
+            </fieldset>
+            </div>
+            <div id="velrest">
+
+            <fieldset class="velocity">
+                <legend>Rest Frequencies</legend>
+                <table>
+                <tr
+                    tal:repeat="i fields/nif"
+                >
+                <td>
+                <label
+                    tal:attributes="id string: if${i}lbl;name  string: if${i}lbl"
+                    tal:content="string: IF${i}"
+                >IF
+                </label>
+                <select
+                    tal:attributes="id string: rest${i};name string: rest${i}"
+                >
+                <option
+                    tal:repeat="line fields/restfreqs"
+                    tal:content="line"
+                    tal:attributes="value repeat/line/index"
+                ></option>
+                </select>
+                &nbsp;
+                </td>
+                </tr>
+                </table>
+            </fieldset>
+            </div>
+            <div style="clear: both;"></div>
+        </fieldset>
+        <p>
+        <fieldset>
+        <legend>Processing</legend>
+            <div class="labeled floatleft">
+            <label>
+                <abbr title="Build the quotient spectrum: Toff * (on/off) - Toff">
+                    Quotient
+                </abbr>
+                <input type="checkbox" name="quotient" tal:attributes="checked python: fields['quotient'] or None;value fields/quotient">
+            </label>
+            </div>
+            <div  class="labeled floatleft">
+            <label>
+                <abbr title="Fit a polynomial baseline to the data">
+                    Baseline
+                </abbr>
+                <input type="checkbox" id="baseline" name="baseline" tal:attributes="checked python: fields['baseline'] or None;value fields/baseline" onClick="baselineswitch();">
+            </label>
+            </div>
+            <div id="polyorder"  class="labeled floatleft">
+                <label>
+                    <abbr title="Order of the polynomial baseline fit">
+                    Order
+                    </abbr>
+                </label>
+                <select name="polyorder">
+                <option tal:repeat="order fields/border"
+                    tal:content="order" tal:attributes="value order;selected python: int(fields['cpolyorder'])==int(repeat['order'].getIndex()) or None"></option>
+                </select>
+            </div>
+            <div id="baselinerange" class="labeled floatleft">
+                <label id="brange">
+                    <abbr title="Specify Windows in pairs, e.g. [min0,max0],[min1,max1]">
+                        Baseline Windows
+                    </abbr>
+                <input id="brangewindow" name="brangewindow"
+                    tal:attributes="value fields/brangewindow"
+                />
+
+                </label>
+                <label id ="brangeunit"></label>
+            </div>
+            <div class="labeled floatleft">
+            <label>
+                <abbr title="Bin the data by 5 channels">
+                    Bin
+                </abbr>
+                <input type="checkbox" name="bin" tal:attributes="checked python: fields['bin'] or None;value fields/bin">
+            </label>
+            </div>
+            <div class="labeled floatleft">
+            <label>
+                <abbr title="Average all integrations in all selected files (Tsys weighted)">Average
+                </abbr>
+                <input type="checkbox" name="average" tal:attributes="checked python: fields['average'] or None;value fields/average">
+            </label>
+            </div>
+            <div class="labeled floatleft">
+            <label>
+                 <abbr title="Display Stokes IQUV instead of raw pols">Stokes
+                </abbr>
+                <input type="checkbox" name="stokes" tal:attributes="checked python: fields['stokes'] or None;value fields/stokes">
+            </label>
+            </div>
+        <div style="clear: both;">
+        </div>
+        </fieldset>
+        <p>
+        <div id="plotrange">
+            <fieldset>
+                <legend>
+                    <abbr title="Set the min/max for the abcissa, e.g 100,200">
+                        Plot Window
+                    </abbr>
+                </legend>
+                <label id="plotlbl"><input id="plotwindow" name="plotwindow" tal:attributes="value fields/plotwindow"></input></label>
+                <label id="prangeunit"></label>
+            </fieldset>
+            </div>
+
+        <p>
+        <div align="center">
+            <input class="plot" type="submit" name="plot" value="plot">
+            <input class="plot" type="button" id="summary" name="summary" value="summary" onclick="javascript:summaryPopUp();">
+        </div>
+    </form>
+    <div align="center">
+        <img tal:attributes="src fields/imagename">
+        <div style="color: red;font-size: 16pt" tal:content="fields/debug"></div>
+    </div>
+</body>
+</html>
Index: tags/Release2.1.0b/monitor/htdocs/asapmon.js
===================================================================
--- tags/Release2.1.0b/monitor/htdocs/asapmon.js	(revision 1196)
+++ tags/Release2.1.0b/monitor/htdocs/asapmon.js	(revision 1196)
@@ -0,0 +1,205 @@
+var repopulate = function() {
+    unitswitch(document.getElementById('cunit').value);
+    baselineswitch();
+    checkbuttons("cdoppler",["doppler0","doppler1"]);
+    checkbuttons("cframe",["frame0","frame1"]);
+    selectFiles();
+    selectIFs();
+    selectSources();
+    var sumtext = document.getElementById('sum').value;
+    if (sumtext == "") {
+        document.getElementById('summary').style.display = 'none';
+    } else {
+        document.getElementById('summary').style.display = 'inline';
+    }
+    var csource = document.getElementById('csource').value;
+    if (csource == "") {
+        document.getElementById('sourcelbl').style.display = 'none';
+    } else {
+        document.getElementById('sourcelbl').style.display = 'inline';
+    }
+}
+
+function summaryPopUp() {
+    var sumtext = document.getElementById('sum').value;
+    if (sumtext == "") {return false;}
+    TheNewWin = window.open('','name','height=500,width=600,toolbar=no,directories=no,status=no,menubar=no');
+    TheNewWin.document.write('<!DOCTYPE html PUBLIC"-//W3C//DTD XHTML 1.0 Transitional//EN" "http:\/\/www.w3.org\/TR\/xhtml1\/DTD\/xhtml1-transitional.dtd"><html xmlns="http:\/\/www.w3.org\/1999\/xhtml">');
+    TheNewWin.document.write('<head><title>Popup<\/title><\/head><body style="overflow:hidden" bgcolor="#ffffff"><pre>');
+    TheNewWin.document.write(sumtext);
+    TheNewWin.document.write('</pre><hr \/> <p align="right"><a href="#" onclick="self.close();return false;">Close');
+    TheNewWin.document.write(' Window<\/a><\/p> <\/body><\/html>');
+    return false;
+}
+
+function checkbuttons(hiddenfields, fields){
+    var theval = document.getElementById(hiddenfields).value;
+    for (i=0; i<fields.length;++i ){
+        var elem = document.getElementById(fields[i]);
+        if ( elem.value == theval ) {
+            elem.checked = 'checked';
+        }
+    }
+}
+
+function selectSources() {
+    var sl = document.getElementById("sourcelist");
+    var cs = document.getElementById("csource").value;
+    var opts = sl.options;
+    for (k=0; k<opts.length;++k) {
+        if (opts[k].value == cs ) {
+            opts[k].selected = 'selected';
+        }
+    }
+    if (cs) {
+        var sourcediv = document.getElementById("sourcelbl");
+        sourcediv.style.display = "inline";
+    }
+}
+
+function selectFiles() {
+    var indeces = eval(document.getElementById("cfilesel").value);
+    var fl = document.getElementById("filelist");
+    var opts = fl.options;
+    for (k=0; k<indeces.length;++k) {
+        opts[indeces[k]].selected = 'selected';
+    }
+}
+
+function selectIFs() {
+    nif = eval(document.getElementById("nif").value);
+    for (i=0; i<nif.length;++i ) {
+        p0 = "cif"+nif[i]
+        p1 = "rest"+nif[i]
+        var ifsel = eval(document.getElementById(p0).value);
+        var f = document.getElementById(p1);
+        f.options[ifsel].selected = 'selected';
+    }
+}
+
+function baselineswitch() {
+    var fields = ["polyorder","baselinerange"];
+    var elem = document.getElementById("baseline");
+    for (i=0; i<fields.length; ++i) {
+        var cont = document.getElementById(fields[i]);
+        if (elem.checked) {
+            elem.value = 1;
+            cont.style.display = "block";
+        } else {
+            cont.style.display = "none";
+            elem.value = 0;
+        }
+    }
+}
+
+function clearunit() {
+    var fields = ["plotwindow","brangewindow"];
+    for (i=0; i<fields.length; ++i) {
+        var cont = document.getElementById(fields[i]);
+        cont.value = "";
+    }
+}
+
+function unitswitch(unitval) {
+    var fields = ["velframe","veldoppler","velrest"];
+    for (i=0; i<fields.length; ++i) {
+        var cont = document.getElementById(fields[i]);
+        if (unitval == "channel") {
+            cont.style.display = "none";
+        } else {
+            if (unitval == "GHz") {
+             if (fields[i] == "velrest" || fields[i] == "veldoppler") cont.style.display = "none";
+             else cont.style.display = "block";
+            } else {
+                cont.style.display = "block";
+            }
+        }
+    }
+    var ulblcont = document.getElementById("prangeunit");
+    ulblcont.innerHTML = "";
+    ulblcont.innerHTML = unitval;
+    var ulblcont = document.getElementById("brangeunit");
+    ulblcont.innerHTML = "";
+    ulblcont.innerHTML = unitval;
+    if ( unitval != document.getElementById('cunit').value ) {
+        clearunit();
+    }
+}
+
+
+function listFiles(name, serviceURL) {
+        var callback = (arguments.length == 3 ? arguments[2] : null);
+        var request = new Ajax.Request(serviceURL, {
+                parameters: "path=" + name,
+                onComplete: function(transport) {
+                        var resolutions = processResolutions(transport.responseXML.documentElement);
+                        if (callback != null) {
+                                callback.process(resolutions);
+                        } else {
+                                return resolutions;
+                        }
+                }
+        });
+}
+
+function processResolutions(result) {
+    //var results = result.getElementsByTagName("Listing");
+
+    var fnames = result.getElementsByTagName("File");
+    var files = [];
+    if (fnames.length > 0 ) {
+        for (i=0;i<fnames.length;++i) {
+            files.push(fnames[i].firstChild.nodeValue)
+        }
+    }
+    return files;
+}
+
+
+function insertFields() {
+    var opts = document.getElementById("directory");
+    var path = opts.selectedIndex;
+    scripturl = new String(window.document.location);
+    scripturl = scripturl.replace(/\w+\.py.*$/gi,"filelist.py");
+    listFiles(path,scripturl, callbackHandler);
+    return;
+}
+
+
+function invalidateSources() {
+    var opts = document.getElementById("sourcelist");
+    var fileopt = opts.options;
+    //clear
+    for (i=0;i<fileopt.length;++i) {
+        fileopt[i] = null;
+    }
+    fileopt.length = 0;
+    var sourcediv = document.getElementById("sourcelbl");
+    sourcediv.style.display = "none";
+}
+
+function invalidateFiles() {
+    insertFields();
+    invalidateSources();
+}
+
+var callbackHandler = {
+    process: function(parm) {
+        var opts = document.getElementById("filelist");
+        var fileopt = opts.options;
+        //clear
+        for (i=0;i<fileopt.length;++i) {
+            fileopt[i] = null;
+        }
+        fileopt.length = 0;
+        for (i=0;i<parm.length;++i) {
+            var opt = document.createElement("option");
+            opt.text = parm[i];
+            opt.value = i;
+            fileopt.add(opt);
+        }
+        // last file is selected
+        if (fileopt.length > 0)
+            opt.selected = fileopt.length-1;
+    }
+}
Index: tags/Release2.1.0b/python/__init__.py
===================================================================
--- tags/Release2.1.0b/python/__init__.py	(revision 1196)
+++ tags/Release2.1.0b/python/__init__.py	(revision 1196)
@@ -0,0 +1,592 @@
+"""
+This is the ATNF Single Dish Analysis package.
+
+"""
+import os,sys,shutil, platform
+
+# Set up AIPSPATH and first time use of asap i.e. ~/.asap/*
+plf = None
+if sys.platform == "linux2":
+    if platform.architecture()[0] == '64bit':
+        plf = 'linux_64b'
+    else:
+        plf = 'linux_gnu'
+elif sys.platform == 'darwin':
+    plf = 'darwin'
+else:
+    # Shouldn't happen - default to linux
+    plf = 'linux'
+asapdata = __path__[-1]
+# Allow user defined data location
+if os.environ.has_key("ASAPDATA"):
+    if os.path.exists(os.environ["ASAPDATA"]):
+        asapdata = os.environ["ASAPDATA"]
+os.environ["AIPSPATH"] = "%s %s somwhere" % ( asapdata, plf)
+# set up user space
+userdir = os.environ["HOME"]+"/.asap"
+if not os.path.exists(userdir):
+    print 'First time ASAP use. Setting up ~/.asap'
+    os.mkdir(userdir)
+    shutil.copyfile(asapdata+"/data/ipythonrc-asap", userdir+"/ipythonrc-asap")
+    f = file(userdir+"/asapuserfuncs.py", "w")
+    f.close()
+    f = file(userdir+"/ipythonrc", "w")
+    f.close()
+# remove from namespace
+del asapdata, userdir, shutil, platform
+
+def _validate_bool(b):
+    'Convert b to a boolean or raise'
+    bl = b.lower()
+    if bl in ('f', 'no', 'false', '0', 0): return False
+    elif bl in ('t', 'yes', 'true', '1', 1): return True
+    else:
+        raise ValueError('Could not convert "%s" to boolean' % b)
+
+def _validate_int(s):
+    'convert s to int or raise'
+    try: return int(s)
+    except ValueError:
+        raise ValueError('Could not convert "%s" to int' % s)
+
+def _asap_fname():
+    """
+    Return the path to the rc file
+
+    Search order:
+
+     * current working dir
+     * environ var ASAPRC
+     * HOME/.asaprc
+
+    """
+
+    fname = os.path.join( os.getcwd(), '.asaprc')
+    if os.path.exists(fname): return fname
+
+    if os.environ.has_key('ASAPRC'):
+        path =  os.environ['ASAPRC']
+        if os.path.exists(path):
+            fname = os.path.join(path, '.asaprc')
+            if os.path.exists(fname):
+                return fname
+
+    if os.environ.has_key('HOME'):
+        home =  os.environ['HOME']
+        fname = os.path.join(home, '.asaprc')
+        if os.path.exists(fname):
+            return fname
+    return None
+
+
+defaultParams = {
+    # general
+    'verbose'             : [True, _validate_bool],
+    'useplotter'          : [True, _validate_bool],
+    'insitu'              : [True, _validate_bool],
+
+    # plotting
+    'plotter.gui'         : [True, _validate_bool],
+    'plotter.stacking'    : ['p', str],
+    'plotter.panelling'   : ['s', str],
+    'plotter.colours'     : ['', str],
+    'plotter.linestyles'  : ['', str],
+    'plotter.decimate'    : [False, _validate_bool],
+    'plotter.ganged'      : [True, _validate_bool],
+    'plotter.histogram'  : [False, _validate_bool],
+    'plotter.papertype'  : ['A4', str],
+
+    # scantable
+    'scantable.save'      : ['ASAP', str],
+    'scantable.autoaverage'      : [True, _validate_bool],
+    'scantable.freqframe' : ['LSRK', str],  #default frequency frame
+    'scantable.verbosesummary'   : [False, _validate_bool],
+    'scantable.storage'   : ['memory', str]
+    # fitter
+    }
+
+def list_rcparameters():
+
+    print """
+# general
+# print verbose output
+verbose                    : True
+
+# preload a default plotter
+useplotter                 : True
+
+# apply operations on the input scantable or return new one
+insitu                     : True
+
+# plotting
+
+# do we want a GUI or plot to a file
+plotter.gui                : True
+
+# default mode for colour stacking
+plotter.stacking           : Pol
+
+# default mode for panelling
+plotter.panelling          : scan
+
+# push panels together, to share axislabels
+plotter.ganged             : True
+
+# decimate the number of points plotted bya afactor of
+# nchan/1024
+plotter.decimate           : False
+
+# default colours/linestyles
+plotter.colours            :
+plotter.linestyles         :
+
+# enable/disable histogram plotting
+plotter.histogram          : False
+
+# ps paper type
+plotter.papertype          : A4
+
+# scantable
+
+# default storage of scantable (memory/disk)
+scantable.storage          : memory
+# default ouput format when saving
+scantable.save             : ASAP
+# auto averaging on read
+scantable.autoaverage      : True
+
+# default frequency frame to set when function
+# scantable.set_freqframe is called
+scantable.freqframe        : LSRK
+
+# Control the level of information printed by summary
+scantable.verbosesummary   : False
+
+# Fitter
+"""
+
+def rc_params():
+    'Return the default params updated from the values in the rc file'
+
+    fname = _asap_fname()
+
+    if fname is None or not os.path.exists(fname):
+        message = 'could not find rc file; returning defaults'
+        ret =  dict([ (key, tup[0]) for key, tup in defaultParams.items()])
+        #print message
+        return ret
+
+    cnt = 0
+    for line in file(fname):
+        cnt +=1
+        line = line.strip()
+        if not len(line): continue
+        if line.startswith('#'): continue
+        tup = line.split(':',1)
+        if len(tup) !=2:
+            print ('Illegal line #%d\n\t%s\n\tin file "%s"' % (cnt, line, fname))
+            continue
+
+        key, val = tup
+        key = key.strip()
+        if not defaultParams.has_key(key):
+            print ('Bad key "%s" on line %d in %s' % (key, cnt, fname))
+            continue
+
+        default, converter =  defaultParams[key]
+
+        ind = val.find('#')
+        if ind>=0: val = val[:ind]   # ignore trailing comments
+        val = val.strip()
+        try: cval = converter(val)   # try to convert to proper type or raise
+        except ValueError, msg:
+            print ('Bad val "%s" on line #%d\n\t"%s"\n\tin file "%s"\n\t%s' % (val, cnt, line, fname, msg))
+            continue
+        else:
+            # Alles Klar, update dict
+            defaultParams[key][0] = cval
+
+    # strip the conveter funcs and return
+    ret =  dict([ (key, tup[0]) for key, tup in defaultParams.items()])
+    print ('loaded rc file %s'%fname)
+
+    return ret
+
+
+# this is the instance used by the asap classes
+rcParams = rc_params()
+
+rcParamsDefault = dict(rcParams.items()) # a copy
+
+def rc(group, **kwargs):
+    """
+    Set the current rc params.  Group is the grouping for the rc, eg
+    for scantable.save the group is 'scantable', for plotter.stacking, the
+    group is 'plotter', and so on.  kwargs is a list of attribute
+    name/value pairs, eg
+
+      rc('scantable', save='SDFITS')
+
+    sets the current rc params and is equivalent to
+
+      rcParams['scantable.save'] = 'SDFITS'
+
+    Use rcdefaults to restore the default rc params after changes.
+    """
+
+    aliases = {}
+
+    for k,v in kwargs.items():
+        name = aliases.get(k) or k
+        key = '%s.%s' % (group, name)
+        if not rcParams.has_key(key):
+            raise KeyError('Unrecognized key "%s" for group "%s" and name "%s"' % (key, group, name))
+
+        rcParams[key] = v
+
+
+def rcdefaults():
+    """
+    Restore the default rc params - the ones that were created at
+    asap load time
+    """
+    rcParams.update(rcParamsDefault)
+
+
+def _is_sequence_or_number(param, ptype=int):
+    if isinstance(param,tuple) or isinstance(param,list):
+        if len(param) == 0: return True # empty list
+        out = True
+        for p in param:
+            out &= isinstance(p,ptype)
+        return out
+    elif isinstance(param, ptype):
+        return True
+    return False
+
+def _to_list(param, ptype=int):
+    if isinstance(param, ptype):
+        if ptype is str: return param.split()
+        else: return [param]
+    if _is_sequence_or_number(param, ptype):
+        return param
+    return None
+
+def unique(x):
+    """
+    Return the unique values in a list
+    Parameters:
+        x:      the list to reduce
+    Examples:
+        x = [1,2,3,3,4]
+        print unique(x)
+        [1,2,3,4]
+    """
+    return dict([ (val, 1) for val in x]).keys()
+
+def list_files(path=".",suffix="rpf"):
+    """
+    Return a list files readable by asap, such as rpf, sdfits, mbf, asap
+    Parameters:
+        path:     The directory to list (default '.')
+        suffix:   The file extension (default rpf)
+    Example:
+        files = list_files("data/","sdfits")
+        print files
+        ['data/2001-09-01_0332_P363.sdfits',
+        'data/2003-04-04_131152_t0002.sdfits',
+        'data/Sgr_86p262_best_SPC.sdfits']
+    """
+    import os
+    if not os.path.isdir(path):
+        return None
+    valid = "rpf sdf sdfits mbf asap".split()
+    if not suffix in valid:
+        return None
+    files = [os.path.expanduser(os.path.expandvars(path+"/"+f)) for f in os.listdir(path)]
+    return filter(lambda x: x.endswith(suffix),files)
+
+# workaround for ipython, which redirects this if banner=0 in ipythonrc
+sys.stdout = sys.__stdout__
+sys.stderr = sys.__stderr__
+
+# Logging
+from asap._asap import Log as _asaplog
+global asaplog
+asaplog=_asaplog()
+if rcParams['verbose']:
+    asaplog.enable()
+else:
+    asaplog.disable()
+
+def print_log():
+    log = asaplog.pop()
+    if len(log) and rcParams['verbose']: print log
+    return
+
+try:
+    import numpy.core as NUM
+except ImportError:
+    try:
+        import numarray as NUM
+    except ImportError:
+        raise ImportError("You need to have numpy or numarray installed")
+
+from asapfitter import fitter
+from asapreader import reader
+from selector import selector
+
+from asapmath import *
+from scantable import scantable
+from asaplinefind import linefinder
+from linecatalog import linecatalog
+
+mask_and = NUM.logical_and
+mask_or = NUM.logical_or
+mask_not = NUM.logical_not
+
+if rcParams['useplotter']:
+    from  asapplotter import asapplotter
+    gui = os.environ.has_key('DISPLAY') and rcParams['plotter.gui']
+    if gui:
+        import pylab as xyplotter
+    plotter = asapplotter(gui)
+    del gui
+
+__date__ = '$Date$'.split()[1]
+__version__  = '2.1b'
+
+def is_ipython():
+    return '__IP' in dir(sys.modules["__main__"]) 
+if is_ipython():
+    def version(): print  "ASAP %s(%s)"% (__version__, __date__)
+    def list_scans(t = scantable):
+        import sys, types
+        globs = sys.modules['__main__'].__dict__.iteritems()
+        print "The user created scantables are:"
+        sts = map(lambda x: x[0], filter(lambda x: isinstance(x[1], t), globs))
+        print filter(lambda x: not x.startswith('_'), sts)
+        return
+
+    def commands():
+        x = """
+    [The scan container]
+        scantable           - a container for integrations/scans
+                              (can open asap/rpfits/sdfits and ms files)
+            copy            - returns a copy of a scan
+            get_scan        - gets a specific scan out of a scantable
+                              (by name or number)
+            drop_scan       - drops a specific scan out of a scantable
+                              (by number)
+            set_selection   - set a new subselection of the data
+            get_selection   - get the current selection object
+            summary         - print info about the scantable contents
+            stats           - get specified statistic of the spectra in
+                              the scantable
+            stddev          - get the standard deviation of the spectra
+                              in the scantable
+            get_tsys        - get the TSys
+            get_time        - get the timestamps of the integrations
+            get_sourcename  - get the source names of the scans
+            get_azimuth     - get the azimuth of the scans
+            get_elevation   - get the elevation of the scans
+            get_parangle    - get the parallactic angle of the scans
+            get_unit        - get the current unit
+            set_unit        - set the abcissa unit to be used from this
+                              point on
+            get_abcissa     - get the abcissa values and name for a given
+                              row (time)
+            set_freqframe   - set the frame info for the Spectral Axis
+                              (e.g. 'LSRK')
+            set_doppler     - set the doppler to be used from this point on
+            set_dirframe    - set the frame for the direction on the sky
+            set_instrument  - set the instrument name
+            set_feedtype    - set the feed type
+            get_fluxunit    - get the brightness flux unit
+            set_fluxunit    - set the brightness flux unit
+            create_mask     - return an mask in the current unit
+                              for the given region. The specified regions
+                              are NOT masked
+            get_restfreqs   - get the current list of rest frequencies
+            set_restfreqs   - set a list of rest frequencies
+            flag            - flag selected channels in the data
+            lag_flag        - flag specified frequency in the data
+            save            - save the scantable to disk as either 'ASAP',
+                              'SDFITS' or 'ASCII'
+            nbeam,nif,nchan,npol - the number of beams/IFs/Pols/Chans
+            nscan           - the number of scans in the scantable
+            nrow            - te number of spectra in the scantable
+            history         - print the history of the scantable
+            get_fit         - get a fit which has been stored witnh the data
+            average_time    - return the (weighted) time average of a scan
+                              or a list of scans
+            average_pol     - average the polarisations together.
+            average_beam    - average the beams together.
+            convert_pol     - convert to a different polarisation type
+            auto_quotient   - return the on/off quotient with
+                              automatic detection of the on/off scans (closest
+                              in time off is selected)
+            mx_quotient     - Form a quotient using MX data (off beams)
+            scale, *, /     - return a scan scaled by a given factor
+            add, +, -       - return a scan with given value added
+            bin             - return a scan with binned channels
+            resample        - return a scan with resampled channels
+            smooth          - return the spectrally smoothed scan
+            poly_baseline   - fit a polynomial baseline to all Beams/IFs/Pols
+            auto_poly_baseline - automatically fit a polynomial baseline
+            recalc_azel     - recalculate azimuth and elevation based on
+                              the pointing
+            gain_el         - apply gain-elevation correction
+            opacity         - apply opacity correction
+            convert_flux    - convert to and from Jy and Kelvin brightness
+                              units
+            freq_align      - align spectra in frequency frame
+            invert_phase    - Invert the phase of the cross-correlation
+            swap_linears    - Swap XX and YY
+            rotate_xyphase  - rotate XY phase of cross correlation
+            rotate_linpolphase - rotate the phase of the complex
+                                 polarization O=Q+iU correlation
+            freq_switch     - perform frequency switching on the data
+            stats           - Determine the specified statistic, e.g. 'min'
+                              'max', 'rms' etc.
+            stddev          - Determine the standard deviation of the current
+                              beam/if/pol
+     [Selection]
+         selector              - a selection object to set a subset of a scantable
+            set_scans          - set (a list of) scans by index
+            set_cycles         - set (a list of) cycles by index
+            set_beams          - set (a list of) beamss by index
+            set_ifs            - set (a list of) ifs by index
+            set_polarisations  - set (a list of) polarisations by name
+                                 or by index
+            set_names          - set a selection by name (wildcards allowed)
+            set_tsys           - set a selection by tsys thresholds
+            reset              - unset all selections
+            +                  - merge to selections
+
+     [Math] Mainly functions which operate on more than one scantable
+
+            average_time    - return the (weighted) time average
+                              of a list of scans
+            quotient        - return the on/off quotient
+            simple_math     - simple mathematical operations on two scantables,
+                              'add', 'sub', 'mul', 'div'
+            quotient        - build quotient of the given on and off scans
+                              (matched pairs and 1 off/n on are valid)
+            merge           - merge a list of scantables
+
+     [Line Catalog]
+        linecatalog              - a linecatalog wrapper, taking an ASCII or
+                                   internal format table
+            summary              - print a summary of the current selection
+            set_name             - select a subset by name pattern, e.g. '*OH*'
+            set_strength_limits  - select a subset by line strength limits
+            set_frequency_limits - select a subset by frequency limits
+            reset                - unset all selections
+            save                 - save the current subset to a table (internal
+                                   format)
+            get_row              - get the name and frequency from a specific
+                                   row in the table
+     [Fitting]
+        fitter
+            auto_fit        - return a scan where the function is
+                              applied to all Beams/IFs/Pols.
+            commit          - return a new scan where the fits have been
+                              commited.
+            fit             - execute the actual fitting process
+            store_fit       - store the fit parameters in the data (scantable)
+            get_chi2        - get the Chi^2
+            set_scan        - set the scantable to be fit
+            set_function    - set the fitting function
+            set_parameters  - set the parameters for the function(s), and
+                              set if they should be held fixed during fitting
+            set_gauss_parameters - same as above but specialised for individual
+                                   gaussian components
+            get_parameters  - get the fitted parameters
+            plot            - plot the resulting fit and/or components and
+                              residual
+    [Plotter]
+        asapplotter         - a plotter for asap, default plotter is
+                              called 'plotter'
+            plot            - plot a scantable
+            plot_lines      - plot a linecatalog overlay
+            save            - save the plot to a file ('png' ,'ps' or 'eps')
+            set_mode        - set the state of the plotter, i.e.
+                              what is to be plotted 'colour stacked'
+                              and what 'panelled'
+            set_selection   - only plot a selected part of the data
+            set_range       - set a 'zoom' window [xmin,xmax,ymin,ymax]
+            set_legend      - specify user labels for the legend indeces
+            set_title       - specify user labels for the panel indeces
+            set_abcissa     - specify a user label for the abcissa
+            set_ordinate    - specify a user label for the ordinate
+            set_layout      - specify the multi-panel layout (rows,cols)
+            set_colors      - specify a set of colours to use
+            set_linestyles  - specify a set of linestyles to use if only
+                              using one color
+            set_font        - set general font properties, e.g. 'family'
+            set_histogram   - plot in historam style
+            set_mask        - set a plotting mask for a specific polarization
+            text            - draw text annotations either in data or relative
+                              coordinates
+            arrow           - draw arrow annotations either in data or relative
+                              coordinates
+            axhline,axvline - draw horizontal/vertical lines
+            axhspan,axvspan - draw horizontal/vertical regions
+
+        xyplotter           - matplotlib/pylab plotting functions
+
+    [Reading files]
+        reader              - access rpfits/sdfits files
+            open            - attach reader to a file
+            close           - detach reader from file
+            read            - read in integrations
+            summary         - list info about all integrations
+
+    [General]
+        commands            - this command
+        print               - print details about a variable
+        list_scans          - list all scantables created bt the user
+        list_files          - list all files readable by asap (default rpf)
+        del                 - delete the given variable from memory
+        range               - create a list of values, e.g.
+                              range(3) = [0,1,2], range(2,5) = [2,3,4]
+        help                - print help for one of the listed functions
+        execfile            - execute an asap script, e.g. execfile('myscript')
+        list_rcparameters   - print out a list of possible values to be
+                              put into $HOME/.asaprc
+        rc                  - set rc parameters from within asap
+        mask_and,mask_or,
+        mask_not            - boolean operations on masks created with
+                              scantable.create_mask
+
+    Note:
+        How to use this with help:
+                                         # function 'summary'
+        [xxx] is just a category
+        Every 'sub-level' in this list should be replaces by a '.' Period when
+        using help
+        Example:
+            ASAP> help scantable # to get info on ths scantable
+            ASAP> help scantable.summary # to get help on the scantable's
+            ASAP> help average_time
+
+            """
+        if rcParams['verbose']:
+            try:
+                from IPython.genutils import page as pager
+            except ImportError:
+                from pydoc import pager
+            pager(x)
+        else:
+            print x
+        return
+
+def welcome():
+    return """Welcome to ASAP v%s (%s) - the ATNF Spectral Analysis Package
+
+Please report any bugs via:
+http://sourcecode.atnf.csiro.au/cgi-bin/trac_asap.cgi/newticket
+
+[IMPORTANT: ASAP is 0-based]
+Type commands() to get a list of all available ASAP commands.""" % (__version__, __date__)
Index: tags/Release2.1.0b/python/asapfit.py
===================================================================
--- tags/Release2.1.0b/python/asapfit.py	(revision 1196)
+++ tags/Release2.1.0b/python/asapfit.py	(revision 1196)
@@ -0,0 +1,70 @@
+from _asap import fitentry
+from asap import rcParams
+
+class asapfit(fitentry):
+
+    def __init__(self, other=None):
+        if isinstance(other, fitentry):
+            fitentry.__init__(self,other)
+        else:
+            fitentry.__init__(self)
+
+    def __str__(self):
+        out = ""
+        out += "Fit:"
+        pars = self.getparameters()
+        mask = self.getfixedparameters()
+        funcs = self.getfunctions()
+        comps = self.getcomponents()
+        finfo = self.getframeinfo()
+        pos=0
+        k = 0
+        for f in funcs:
+            out += "\n Type:         "
+            out += f
+            s = pos
+            pos += comps[k]
+            ps = pars[s:pos]
+            out += "\n  Parameters:  "
+            out += self._format_pars(pars[s:pos],f, finfo[0])
+            out += "\n  Fixed Parms: "
+            out += str(mask[s:pos])
+            out += "\n  Frame:       "
+            out += str(finfo)
+            out += "\n"
+        out += "\n"
+        return out
+
+    def as_dict(self):
+        out = []
+        pars = self.getparameters()
+        mask = self.getfixedparameters()
+        funcs = self.getfunctions()
+        comps = self.getcomponents()
+        pos=0
+        k=0
+        comp = []
+        for f in funcs:
+            s = pos
+            pos += comps[k]
+            ps = pars[s:pos]
+            d = {'function' : f,
+                  'parameters' : pars[s:pos],
+                  'fixed' : mask[s:pos],
+                  'frame' : self.getframeinfo()
+                  }
+            comp.append(d)
+        out.append(comp)
+        return out
+
+    def _format_pars(self, pars, ftype, unit):
+        out = ''
+        if ftype == 'poly':
+            i = 0
+            for p in pars:
+                out += ' p%d = %3.3f %s,' % (i,p,unit)
+                i+=1
+            out = out[1:-1]
+        elif ftype == 'gauss':
+            out += 'peak = %3.3f , centre = %3.3f %s, FWHM = %3.3f %s' % (pars[0],pars[1],unit,pars[2],unit)
+        return out
Index: tags/Release2.1.0b/python/asapfitter.py
===================================================================
--- tags/Release2.1.0b/python/asapfitter.py	(revision 1196)
+++ tags/Release2.1.0b/python/asapfitter.py	(revision 1196)
@@ -0,0 +1,579 @@
+import _asap
+from asap import rcParams
+from asap import print_log
+from asap import NUM
+
+class fitter:
+    """
+    The fitting class for ASAP.
+    """
+
+    def __init__(self):
+        """
+        Create a fitter object. No state is set.
+        """
+        self.fitter = _asap.fitter()
+        self.x = None
+        self.y = None
+        self.mask = None
+        self.fitfunc = None
+        self.fitfuncs = None
+        self.fitted = False
+        self.data = None
+        self.components = 0
+        self._fittedrow = 0
+        self._p = None
+        self._selection = None
+
+    def set_data(self, xdat, ydat, mask=None):
+        """
+        Set the absissa and ordinate for the fit. Also set the mask
+        indicationg valid points.
+        This can be used for data vectors retrieved from a scantable.
+        For scantable fitting use 'fitter.set_scan(scan, mask)'.
+        Parameters:
+            xdat:    the abcissa values
+            ydat:    the ordinate values
+            mask:    an optional mask
+
+        """
+        self.fitted = False
+        self.x = xdat
+        self.y = ydat
+        if mask == None:
+            self.mask = NUM.ones(len(xdat))
+        else:
+            self.mask = mask
+        return
+
+    def set_scan(self, thescan=None, mask=None):
+        """
+        Set the 'data' (a scantable) of the fitter.
+        Parameters:
+            thescan:     a scantable
+            mask:        a msk retireved from the scantable
+        """
+        if not thescan:
+            msg = "Please give a correct scan"
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise TypeError(msg)
+        self.fitted = False
+        self.data = thescan
+        self.mask = None
+        if mask is None:
+            self.mask = NUM.ones(self.data.nchan())
+        else:
+            self.mask = mask
+        return
+
+    def set_function(self, **kwargs):
+        """
+        Set the function to be fit.
+        Parameters:
+            poly:    use a polynomial of the order given
+            gauss:   fit the number of gaussian specified
+        Example:
+            fitter.set_function(gauss=2) # will fit two gaussians
+            fitter.set_function(poly=3)  # will fit a 3rd order polynomial
+        """
+        #default poly order 0
+        n=0
+        if kwargs.has_key('poly'):
+            self.fitfunc = 'poly'
+            n = kwargs.get('poly')
+            self.components = [n]
+        elif kwargs.has_key('gauss'):
+            n = kwargs.get('gauss')
+            self.fitfunc = 'gauss'
+            self.fitfuncs = [ 'gauss' for i in range(n) ]
+            self.components = [ 3 for i in range(n) ]
+        else:
+            msg = "Invalid function type."
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise TypeError(msg)
+
+        self.fitter.setexpression(self.fitfunc,n)
+        return
+
+    def fit(self, row=0, estimate=False):
+        """
+        Execute the actual fitting process. All the state has to be set.
+        Parameters:
+            row:        specify the row in the scantable
+            estimate:   auto-compute an initial parameter set (default False)
+                        This can be used to compute estimates even if fit was
+                        called before.
+        Example:
+            s = scantable('myscan.asap')
+            s.set_cursor(thepol=1)        # select second pol
+            f = fitter()
+            f.set_scan(s)
+            f.set_function(poly=0)
+            f.fit(row=0)                  # fit first row
+        """
+        if ((self.x is None or self.y is None) and self.data is None) \
+               or self.fitfunc is None:
+            msg = "Fitter not yet initialised. Please set data & fit function"
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise RuntimeError(msg)
+
+        else:
+            if self.data is not None:
+                self.x = self.data._getabcissa(row)
+                self.y = self.data._getspectrum(row)
+                from asap import asaplog
+                asaplog.push("Fitting:")
+                i = row
+                out = "Scan[%d] Beam[%d] IF[%d] Pol[%d] Cycle[%d]" % (self.data.getscan(i),self.data.getbeam(i),self.data.getif(i),self.data.getpol(i), self.data.getcycle(i))
+                asaplog.push(out,False)
+        self.fitter.setdata(self.x, self.y, self.mask)
+        if self.fitfunc == 'gauss':
+            ps = self.fitter.getparameters()
+            if len(ps) == 0 or estimate:
+                self.fitter.estimate()
+        try:
+            converged = self.fitter.fit()
+            if not converged:
+                raise RuntimeError,"Fit didn't converge."
+        except RuntimeError, msg:
+            if rcParams['verbose']:
+                print msg
+            else:
+                raise
+        self._fittedrow = row
+        self.fitted = True
+        print_log()
+        return
+
+    def store_fit(self):
+        """
+        Store the fit parameters in the scantable.
+        """
+        if self.fitted and self.data is not None:
+            pars = list(self.fitter.getparameters())
+            fixed = list(self.fitter.getfixedparameters())
+            from asap.asapfit import asapfit
+            fit = asapfit()
+            fit.setparameters(pars)
+            fit.setfixedparameters(fixed)
+            fit.setfunctions(self.fitfuncs)
+            fit.setcomponents(self.components)
+            fit.setframeinfo(self.data._getcoordinfo())
+            self.data._addfit(fit,self._fittedrow)
+
+    #def set_parameters(self, params, fixed=None, component=None):
+    def set_parameters(self,*args,**kwargs):
+        """
+        Set the parameters to be fitted.
+        Parameters:
+              params:    a vector of parameters
+              fixed:     a vector of which parameters are to be held fixed
+                         (default is none)
+              component: in case of multiple gaussians, the index of the
+                         component
+        """
+        component = None
+        fixed = None
+        params = None
+
+        if len(args) and isinstance(args[0],dict):
+            kwargs = args[0]
+        if kwargs.has_key("fixed"): fixed = kwargs["fixed"]
+        if kwargs.has_key("params"): params = kwargs["params"]
+        if len(args) == 2 and isinstance(args[1], int):
+            component = args[1]
+        if self.fitfunc is None:
+            msg = "Please specify a fitting function first."
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise RuntimeError(msg)
+        if self.fitfunc == "gauss" and component is not None:
+            if not self.fitted and sum(self.fitter.getparameters()) == 0:
+                pars = list(NUM.zeros(len(self.components)*3))
+                fxd = list(NUM.zeros(len(pars)))
+            else:
+                pars = list(self.fitter.getparameters())
+                fxd = list(self.fitter.getfixedparameters())
+            i = 3*component
+            pars[i:i+3] = params
+            fxd[i:i+3] = fixed
+            params = pars
+            fixed = fxd
+        self.fitter.setparameters(params)
+        if fixed is not None:
+            self.fitter.setfixedparameters(fixed)
+        print_log()
+        return
+
+    def set_gauss_parameters(self, peak, centre, fhwm,
+                             peakfixed=0, centerfixed=0,
+                             fhwmfixed=0,
+                             component=0):
+        """
+        Set the Parameters of a 'Gaussian' component, set with set_function.
+        Parameters:
+            peak, centre, fhwm:  The gaussian parameters
+            peakfixed,
+            centerfixed,
+            fhwmfixed:           Optional parameters to indicate if
+                                 the paramters should be held fixed during
+                                 the fitting process. The default is to keep
+                                 all parameters flexible.
+            component:           The number of the component (Default is the
+                                 component 0)
+        """
+        if self.fitfunc != "gauss":
+            msg = "Function only operates on Gaussian components."
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise ValueError(msg)
+        if 0 <= component < len(self.components):
+            d = {'params':[peak, centre, fhwm],
+                 'fixed':[peakfixed, centerfixed, fhwmfixed]}
+            self.set_parameters(d, component)
+        else:
+            msg = "Please select a valid  component."
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise ValueError(msg)
+
+    def get_area(self, component=None):
+        """
+        Return the area under the fitted gaussian component.
+        Parameters:
+              component:   the gaussian component selection,
+                           default (None) is the sum of all components
+        Note:
+              This will only work for gaussian fits.
+        """
+        if not self.fitted: return
+        if self.fitfunc == "gauss":
+            pars = list(self.fitter.getparameters())
+            from math import log,pi,sqrt
+            fac = sqrt(pi/log(16.0))
+            areas = []
+            for i in range(len(self.components)):
+                j = i*3
+                cpars = pars[j:j+3]
+                areas.append(fac * cpars[0] * cpars[2])
+        else:
+            return None
+        if component is not None:
+            return areas[component]
+        else:
+            return sum(areas)
+
+    def get_errors(self, component=None):
+        """
+        Return the errors in the parameters.
+        Parameters:
+            component:    get the errors for the specified component
+                          only, default is all components
+        """
+        if not self.fitted:
+            msg = "Not yet fitted."
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise RuntimeError(msg)
+        errs = list(self.fitter.geterrors())
+        cerrs = errs
+        if component is not None:
+            if self.fitfunc == "gauss":
+                i = 3*component
+                if i < len(errs):
+                    cerrs = errs[i:i+3]
+        return cerrs
+
+    def get_parameters(self, component=None, errors=False):
+        """
+        Return the fit paramters.
+        Parameters:
+             component:    get the parameters for the specified component
+                           only, default is all components
+        """
+        if not self.fitted:
+            msg = "Not yet fitted."
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise RuntimeError(msg)
+        pars = list(self.fitter.getparameters())
+        fixed = list(self.fitter.getfixedparameters())
+        errs = list(self.fitter.geterrors())
+        area = []
+        if component is not None:
+            if self.fitfunc == "gauss":
+                i = 3*component
+                cpars = pars[i:i+3]
+                cfixed = fixed[i:i+3]
+                cerrs = errs[i:i+3]
+                a = self.get_area(component)
+                area = [a for i in range(3)]
+            else:
+                cpars = pars
+                cfixed = fixed
+                cerrs = errs
+        else:
+            cpars = pars
+            cfixed = fixed
+            cerrs = errs
+            if self.fitfunc == "gauss":
+                for c in range(len(self.components)):
+                  a = self.get_area(c)
+                  area += [a for i in range(3)]
+        fpars = self._format_pars(cpars, cfixed, errors and cerrs, area)
+        if rcParams['verbose']:
+            print fpars
+        return {'params':cpars, 'fixed':cfixed, 'formatted': fpars,
+                'errors':cerrs}
+
+    def _format_pars(self, pars, fixed, errors, area):
+        out = ''
+        if self.fitfunc == 'poly':
+            c = 0
+            for i in range(len(pars)):
+                fix = ""
+                if fixed[i]: fix = "(fixed)"
+                if errors :
+                    out += '  p%d%s= %3.6f (%1.6f),' % (c,fix,pars[i], errors[i])
+                else:
+                    out += '  p%d%s= %3.6f,' % (c,fix,pars[i])
+                c+=1
+            out = out[:-1]  # remove trailing ','
+        elif self.fitfunc == 'gauss':
+            i = 0
+            c = 0
+            aunit = ''
+            ounit = ''
+            if self.data:
+                aunit = self.data.get_unit()
+                ounit = self.data.get_fluxunit()
+            while i < len(pars):
+                if len(area):
+                    out += '  %2d: peak = %3.3f %s , centre = %3.3f %s, FWHM = %3.3f %s\n      area = %3.3f %s %s\n' % (c,pars[i],ounit,pars[i+1],aunit,pars[i+2],aunit, area[i],ounit,aunit)
+                else:
+                    out += '  %2d: peak = %3.3f %s , centre = %3.3f %s, FWHM = %3.3f %s\n' % (c,pars[i],ounit,pars[i+1],aunit,pars[i+2],aunit,ounit,aunit)
+                c+=1
+                i+=3
+        return out
+
+    def get_estimate(self):
+        """
+        Return the parameter estimates (for non-linear functions).
+        """
+        pars = self.fitter.getestimate()
+        fixed = self.fitter.getfixedparameters()
+        if rcParams['verbose']:
+            print self._format_pars(pars,fixed,None)
+        return pars
+
+    def get_residual(self):
+        """
+        Return the residual of the fit.
+        """
+        if not self.fitted:
+            msg = "Not yet fitted."
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise RuntimeError(msg)
+        return self.fitter.getresidual()
+
+    def get_chi2(self):
+        """
+        Return chi^2.
+        """
+        if not self.fitted:
+            msg = "Not yet fitted."
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise RuntimeError(msg)
+        ch2 = self.fitter.getchi2()
+        if rcParams['verbose']:
+            print 'Chi^2 = %3.3f' % (ch2)
+        return ch2
+
+    def get_fit(self):
+        """
+        Return the fitted ordinate values.
+        """
+        if not self.fitted:
+            msg = "Not yet fitted."
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise RuntimeError(msg)
+        return self.fitter.getfit()
+
+    def commit(self):
+        """
+        Return a new scan where the fits have been commited (subtracted)
+        """
+        if not self.fitted:
+            msg = "Not yet fitted."
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise RuntimeError(msg)
+        from asap import scantable
+        if not isinstance(self.data, scantable):
+            msg = "Not a scantable"
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise TypeError(msg)
+        scan = self.data.copy()
+        scan._setspectrum(self.fitter.getresidual())
+        print_log()
+        return scan
+
+    def plot(self, residual=False, components=None, plotparms=False, filename=None):
+        """
+        Plot the last fit.
+        Parameters:
+            residual:    an optional parameter indicating if the residual
+                         should be plotted (default 'False')
+            components:  a list of components to plot, e.g [0,1],
+                         -1 plots the total fit. Default is to only
+                         plot the total fit.
+            plotparms:   Inidicates if the parameter values should be present
+                         on the plot
+        """
+        if not self.fitted:
+            return
+        if not self._p or self._p.is_dead:
+            if rcParams['plotter.gui']:
+                from asap.asaplotgui import asaplotgui as asaplot
+            else:
+                from asap.asaplot import asaplot
+            self._p = asaplot()
+        self._p.hold()
+        self._p.clear()
+        self._p.set_panels()
+        self._p.palette(0)
+        tlab = 'Spectrum'
+        xlab = 'Abcissa'
+        ylab = 'Ordinate'
+        m = None
+        if self.data:
+            tlab = self.data._getsourcename(self._fittedrow)
+            xlab = self.data._getabcissalabel(self._fittedrow)
+            m = self.data._getmask(self._fittedrow)
+            ylab = self.data._get_ordinate_label()
+
+        colours = ["#777777","#dddddd","red","orange","purple","green","magenta", "cyan"]
+        self._p.palette(0,colours)
+        self._p.set_line(label='Spectrum')
+        from matplotlib.numerix import ma,logical_not,array
+        y = ma.masked_array(self.y,mask=logical_not(array(m,copy=False)))
+        self._p.plot(self.x, y)
+        if residual:
+            self._p.palette(1)
+            self._p.set_line(label='Residual')
+            y = ma.masked_array(self.get_residual(),
+                                  mask=logical_not(array(m,copy=False)))
+            self._p.plot(self.x, y)
+        self._p.palette(2)
+        if components is not None:
+            cs = components
+            if isinstance(components,int): cs = [components]
+            if plotparms:
+                self._p.text(0.15,0.15,str(self.get_parameters()['formatted']),size=8)
+            n = len(self.components)
+            self._p.palette(3)
+            for c in cs:
+                if 0 <= c < n:
+                    lab = self.fitfuncs[c]+str(c)
+                    self._p.set_line(label=lab)
+                    y = ma.masked_array(self.fitter.evaluate(c),
+                                          mask=logical_not(array(m,copy=False)))
+
+                    self._p.plot(self.x, y)
+                elif c == -1:
+                    self._p.palette(2)
+                    self._p.set_line(label="Total Fit")
+                    y = ma.masked_array(self.fitter.getfit(),
+                                          mask=logical_not(array(m,copy=False)))
+                    self._p.plot(self.x, y)
+        else:
+            self._p.palette(2)
+            self._p.set_line(label='Fit')
+            y = ma.masked_array(self.fitter.getfit(),
+                                  mask=logical_not(array(m,copy=False)))
+            self._p.plot(self.x, y)
+        xlim=[min(self.x),max(self.x)]
+        self._p.axes.set_xlim(xlim)
+        self._p.set_axes('xlabel',xlab)
+        self._p.set_axes('ylabel',ylab)
+        self._p.set_axes('title',tlab)
+        self._p.release()
+        if (not rcParams['plotter.gui']):
+            self._p.save(filename)
+        print_log()
+
+    def auto_fit(self, insitu=None, plot=False):
+        """
+        Return a scan where the function is applied to all rows for
+        all Beams/IFs/Pols.
+
+        """
+        from asap import scantable
+        if not isinstance(self.data, scantable) :
+            msg = "Data is not a scantable"
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise TypeError(msg)
+        if insitu is None: insitu = rcParams['insitu']
+        if not insitu:
+            scan = self.data.copy()
+        else:
+            scan = self.data
+        rows = xrange(scan.nrow())
+        from asap import asaplog
+        asaplog.push("Fitting:")
+        for r in rows:
+            out = " Scan[%d] Beam[%d] IF[%d] Pol[%d] Cycle[%d]" % (scan.getscan(r),scan.getbeam(r),scan.getif(r),scan.getpol(r), scan.getcycle(r))
+            asaplog.push(out, False)
+            self.x = scan._getabcissa(r)
+            self.y = scan._getspectrum(r)
+            self.data = None
+            self.fit()
+            x = self.get_parameters()
+            if plot:
+                self.plot(residual=True)
+                x = raw_input("Accept fit ([y]/n): ")
+                if x.upper() == 'N':
+                    continue
+            scan._setspectrum(self.fitter.getresidual(), r)
+        if plot:
+            self._p.unmap()
+            self._p = None
+        print_log()
+        return scan
+
Index: tags/Release2.1.0b/python/asaplinefind.py
===================================================================
--- tags/Release2.1.0b/python/asaplinefind.py	(revision 1196)
+++ tags/Release2.1.0b/python/asaplinefind.py	(revision 1196)
@@ -0,0 +1,128 @@
+import _asap
+
+class linefinder:
+    """
+    The class for automated spectral line search in ASAP.
+
+    Example:
+       fl=linefinder()
+       fl.set_scan(sc)
+       fl.set_options(threshold=3)
+       nlines=fl.find_lines(edge=(50,0))
+       if nlines!=0:
+          print "Found ",nlines," spectral lines"
+          print fl.get_ranges(False)
+       else:
+          print "No lines found!"
+       sc2=sc.poly_baseline(fl.get_mask(),7)
+
+    The algorithm involves a simple threshold criterion. The line is
+    considered to be detected if a specified number of consequtive
+    channels (default is 3) is brighter (with respect to the current baseline
+    estimate) than the threshold times the noise level. This criterion is
+    applied in the iterative procedure updating baseline estimate and trying
+    reduced spectral resolutions to detect broad lines as well. The off-line
+    noise level is determined at each iteration as an average of 80% of the
+    lowest variances across the spectrum (i.e. histogram equalization is
+    used to avoid missing weak lines if strong ones are present). For
+    bad baseline shapes it is recommended to increase the threshold and
+    possibly switch the averaging option off (see set_options) to
+    detect strong lines only, fit a high order baseline and repeat the line
+    search.
+
+    """
+
+    def __init__(self):
+        """
+        Create a line finder object.
+        """
+        self.finder = _asap.linefinder()
+        return
+
+    def set_options(self,threshold=1.7320508075688772,min_nchan=3,
+        avg_limit=8,box_size=0.2):
+        """
+        Set the parameters of the algorithm
+        Parameters:
+             threshold    a single channel S/N ratio above which the
+                          channel is considered to be a detection
+                          Default is sqrt(3), which together with
+                          min_nchan=3 gives a 3-sigma criterion
+             min_nchan    a minimal number of consequtive channels,
+                          which should satisfy a threshold criterion to
+                          be a detection. Default is 3.
+             avg_limit    A number of consequtive channels not greater than
+                          this parameter can be averaged to search for
+                          broad lines. Default is 8.
+             box_size     A running mean box size specified as a fraction
+                          of the total spectrum length. Default is 1/5
+        Note:  For bad baselines threshold should be increased,
+               and avg_limit decreased (or even switched off completely by
+               setting this parameter to 1) to avoid detecting baseline
+               undulations instead of real lines.
+        """
+        self.finder.setoptions(threshold,min_nchan,avg_limit,box_size)
+        return
+
+    def set_scan(self, scan):
+        """
+        Set the 'data' (scantable) to work with.
+        Parameters:
+             scan:    a scantable
+        """
+        if not scan:
+           raise RuntimeError, 'Please give a correct scan'
+        self.finder.setscan(scan)
+
+    def find_lines(self,nRow=0,mask=[],edge=(0,0)):
+        """
+        Search for spectral lines in the scan assigned in set_scan.
+        Parameters:
+	     nRow:       a row in the scantable to work with
+             mask:       an optional mask (e.g. retreived from scantable)
+             edge:       an optional number of channels to drop at
+                         the edge of the spectrum. If only one value is
+                         specified, the same number will be dropped from
+                         both sides of the spectrum. Default is to keep
+                         all channels
+        A number of lines found will be returned
+        """
+        if isinstance(edge,int):
+           edge=(edge,)
+
+        from asap import _is_sequence_or_number as _is_valid
+
+        if not _is_valid(edge, int):
+           raise RuntimeError, "Parameter 'edge' has to be an integer or \
+           a pair of integers specified as a tuple"
+
+        if len(edge)>2:
+           raise RuntimeError, "The edge parameter should have two \
+           or less elements"
+        return self.finder.findlines(mask,list(edge),nRow)
+    def get_mask(self,invert=False):
+        """
+        Get the mask to mask out all lines that have been found (default)
+
+        Parameters:
+              invert  if True, only channels belong to lines will be unmasked
+
+        Note: all channels originally masked by the input mask or
+              dropped out by the edge parameter will still be excluded
+              regardless on the invert option
+        """
+        return self.finder.getmask(invert)
+    def get_ranges(self,defunits=True):
+        """
+        Get ranges (start and end channels or velocities) for all spectral
+        lines found.
+
+        Parameters:
+              defunits  if True (default), the range will use the same units
+                        as set for the scan (e.g. LSR velocity)
+                        if False, the range will be expressed in channels
+        """
+        if (defunits):
+            return self.finder.getlineranges()
+        else:
+            return self.finder.getlinerangesinchannels()
Index: tags/Release2.1.0b/python/asaplot.py
===================================================================
--- tags/Release2.1.0b/python/asaplot.py	(revision 1196)
+++ tags/Release2.1.0b/python/asaplot.py	(revision 1196)
@@ -0,0 +1,23 @@
+"""
+ASAP plotting class based on matplotlib.
+"""
+
+from asap.asaplotbase import *
+
+from matplotlib.backends.backend_agg import FigureCanvasAgg
+
+class asaplot(asaplotbase):
+    """
+    ASAP plotting class based on matplotlib.
+    """
+    def __init__(self, rows=1, cols=0, title='', size=(8,4), buffering=False):
+	"""
+	Create a new instance of the ASAPlot plotting class.
+
+	If rows < 1 then a separate call to set_panels() is required to define
+	the panel layout; refer to the doctext for set_panels().
+	"""
+        v = vars()
+        del v['self']
+        asaplotbase.__init__(self,**v)
+        self.canvas = FigureCanvasAgg(self.figure)
Index: tags/Release2.1.0b/python/asaplotbase.py
===================================================================
--- tags/Release2.1.0b/python/asaplotbase.py	(revision 1196)
+++ tags/Release2.1.0b/python/asaplotbase.py	(revision 1196)
@@ -0,0 +1,785 @@
+"""
+ASAP plotting class based on matplotlib.
+"""
+
+import sys
+from re import match
+
+import matplotlib
+
+from matplotlib.figure import Figure, Text
+from matplotlib.font_manager import FontProperties as FP
+from matplotlib.numerix import sqrt
+from matplotlib import rc, rcParams
+from asap import rcParams as asaprcParams
+from matplotlib.ticker import ScalarFormatter
+from matplotlib.ticker import NullLocator
+from matplotlib.transforms import blend_xy_sep_transform
+
+if int(matplotlib.__version__.split(".")[1]) < 87:
+    print "Warning: matplotlib version < 0.87. This might cause errors. Please upgrade."
+
+class MyFormatter(ScalarFormatter):
+    def __call__(self, x, pos=None):
+        #last = len(self.locs)-2
+        if pos==0:
+            return ''
+        else: return ScalarFormatter.__call__(self, x, pos)
+
+class asaplotbase:
+    """
+    ASAP plotting base class based on matplotlib.
+    """
+
+    def __init__(self, rows=1, cols=0, title='', size=(8,6), buffering=False):
+        """
+        Create a new instance of the ASAPlot plotting class.
+
+        If rows < 1 then a separate call to set_panels() is required to define
+        the panel layout; refer to the doctext for set_panels().
+        """
+        self.is_dead = False
+        self.figure = Figure(figsize=size, facecolor='#ddddee')
+        self.canvas = None
+
+        self.set_title(title)
+        self.subplots = []
+        if rows > 0:
+            self.set_panels(rows, cols)
+
+        # Set matplotlib default colour sequence.
+        self.colormap = "green red black cyan magenta orange blue purple yellow pink".split()
+
+        c = asaprcParams['plotter.colours']
+        if isinstance(c,str) and len(c) > 0:
+            self.colormap = c.split()
+
+        self.lsalias = {"line":  [1,0],
+                        "dashdot": [4,2,1,2],
+                        "dashed" : [4,2,4,2],
+                        "dotted" : [1,2],
+                        "dashdotdot": [4,2,1,2,1,2],
+                        "dashdashdot": [4,2,4,2,1,2]
+                        }
+
+        styles = "line dashed dotted dashdot".split()
+        c = asaprcParams['plotter.linestyles']
+        if isinstance(c,str) and len(c) > 0:
+            styles = c.split()
+        s = []
+        for ls in styles:
+            if self.lsalias.has_key(ls):
+                s.append(self.lsalias.get(ls))
+            else:
+                s.append('-')
+        self.linestyles = s
+
+        self.color = 0;
+        self.linestyle = 0;
+        self.attributes = {}
+        self.loc = 0
+
+        self.buffering = buffering
+
+    def clear(self):
+        """
+        Delete all lines from the plot.  Line numbering will restart from 0.
+        """
+
+        for i in range(len(self.lines)):
+           self.delete(i)
+        self.axes.clear()
+        self.color = 0
+        self.lines = []
+
+    def palette(self, color, colormap=None, linestyle=0, linestyles=None):
+        if colormap:
+            if isinstance(colormap,list):
+                self.colormap = colormap
+            elif isinstance(colormap,str):
+                self.colormap = colormap.split()
+        if 0 <= color < len(self.colormap):
+            self.color = color
+        if linestyles:
+            self.linestyles = []
+            if isinstance(linestyles,list):
+                styles = linestyles
+            elif isinstance(linestyles,str):
+                styles = linestyles.split()
+            for ls in styles:
+                if self.lsalias.has_key(ls):
+                    self.linestyles.append(self.lsalias.get(ls))
+                else:
+                    self.linestyles.append(self.lsalias.get('line'))
+        if 0 <= linestyle < len(self.linestyles):
+            self.linestyle = linestyle
+
+    def delete(self, numbers=None):
+        """
+        Delete the 0-relative line number, default is to delete the last.
+        The remaining lines are NOT renumbered.
+        """
+
+        if numbers is None: numbers = [len(self.lines)-1]
+
+        if not hasattr(numbers, '__iter__'):
+            numbers = [numbers]
+
+        for number in numbers:
+            if 0 <= number < len(self.lines):
+                if self.lines[number] is not None:
+                    for line in self.lines[number]:
+                        line.set_linestyle('None')
+                        self.lines[number] = None
+        self.show()
+
+    def get_line(self):
+        """
+        Get the current default line attributes.
+        """
+        return self.attributes
+
+
+    def hist(self, x=None, y=None, fmt=None, add=None):
+        """
+        Plot a histogram.  N.B. the x values refer to the start of the
+        histogram bin.
+
+        fmt is the line style as in plot().
+        """
+        from matplotlib.numerix import array
+        from matplotlib.numerix.ma import MaskedArray
+        if x is None:
+            if y is None: return
+            x = range(len(y))
+
+        if len(x) != len(y):
+            return
+        l2 = 2*len(x)
+        x2 = range(l2)
+        y2 = range(12)
+        y2 = range(l2)
+        m2 = range(l2)
+        ymsk = y.raw_mask()
+        ydat = y.raw_data()
+        for i in range(l2):
+            x2[i] = x[i/2]
+            m2[i] = ymsk[i/2]
+
+        y2[0] = 0.0
+        for i in range(1,l2):
+            y2[i] = ydat[(i-1)/2]
+
+        self.plot(x2, MaskedArray(y2,mask=m2,copy=0), fmt, add)
+
+
+    def hold(self, hold=True):
+        """
+        Buffer graphics until subsequently released.
+        """
+        self.buffering = hold
+
+
+    def legend(self, loc=None):
+        """
+        Add a legend to the plot.
+
+        Any other value for loc else disables the legend:
+             1: upper right
+             2: upper left
+             3: lower left
+             4: lower right
+             5: right
+             6: center left
+             7: center right
+             8: lower center
+             9: upper center
+            10: center
+
+        """
+        if isinstance(loc, int):
+            self.loc = None
+            if 0 <= loc <= 10: self.loc = loc
+        else:
+            self.loc = None
+        #self.show()
+
+
+    def plot(self, x=None, y=None, fmt=None, add=None):
+        """
+        Plot the next line in the current frame using the current line
+        attributes.  The ASAPlot graphics window will be mapped and raised.
+
+        The argument list works a bit like the matlab plot() function.
+        """
+        if x is None:
+            if y is None: return
+            x = range(len(y))
+
+        elif y is None:
+            y = x
+            x = range(len(y))
+        if fmt is None:
+            line = self.axes.plot(x, y)
+        else:
+            line = self.axes.plot(x, y, fmt)
+
+        # Add to an existing line?
+        i = None
+        if add is None or len(self.lines) < add < 0:
+            # Don't add.
+            self.lines.append(line)
+            i = len(self.lines) - 1
+        else:
+            if add == 0: add = len(self.lines)
+            i = add - 1
+            self.lines[i].extend(line)
+
+        # Set/reset attributes for the line.
+        gotcolour = False
+        for k, v in self.attributes.iteritems():
+            if k == 'color': gotcolour = True
+            for segment in self.lines[i]:
+                getattr(segment, "set_%s"%k)(v)
+
+        if not gotcolour and len(self.colormap):
+            for segment in self.lines[i]:
+                getattr(segment, "set_color")(self.colormap[self.color])
+                if len(self.colormap)  == 1:
+                    getattr(segment, "set_dashes")(self.linestyles[self.linestyle])
+
+            self.color += 1
+            if self.color >= len(self.colormap):
+                self.color = 0
+
+            if len(self.colormap) == 1:
+                self.linestyle += 1
+            if self.linestyle >= len(self.linestyles):
+                self.linestyle = 0
+
+        self.show()
+
+
+    def position(self):
+        """
+        Use the mouse to get a position from a graph.
+        """
+
+        def position_disable(event):
+            self.register('button_press', None)
+            print '%.4f, %.4f' % (event.xdata, event.ydata)
+
+        print 'Press any mouse button...'
+        self.register('button_press', position_disable)
+
+
+    def region(self):
+        """
+        Use the mouse to get a rectangular region from a plot.
+
+        The return value is [x0, y0, x1, y1] in world coordinates.
+        """
+
+        def region_start(event):
+            height = self.canvas.figure.bbox.height()
+            self.rect = {'fig': None, 'height': height,
+                         'x': event.x, 'y': height - event.y,
+                         'world': [event.xdata, event.ydata,
+                                   event.xdata, event.ydata]}
+            self.register('button_press', None)
+            self.register('motion_notify', region_draw)
+            self.register('button_release', region_disable)
+
+        def region_draw(event):
+            self.canvas._tkcanvas.delete(self.rect['fig'])
+            self.rect['fig'] = self.canvas._tkcanvas.create_rectangle(
+                                self.rect['x'], self.rect['y'],
+                                event.x, self.rect['height'] - event.y)
+
+        def region_disable(event):
+            self.register('motion_notify', None)
+            self.register('button_release', None)
+
+            self.canvas._tkcanvas.delete(self.rect['fig'])
+
+            self.rect['world'][2:4] = [event.xdata, event.ydata]
+            print '(%.2f, %.2f)  (%.2f, %.2f)' % (self.rect['world'][0],
+                self.rect['world'][1], self.rect['world'][2],
+                self.rect['world'][3])
+
+        self.register('button_press', region_start)
+
+        # This has to be modified to block and return the result (currently
+        # printed by region_disable) when that becomes possible in matplotlib.
+
+        return [0.0, 0.0, 0.0, 0.0]
+
+
+    def register(self, type=None, func=None):
+        """
+        Register, reregister, or deregister events of type 'button_press',
+        'button_release', or 'motion_notify'.
+
+        The specified callback function should have the following signature:
+
+            def func(event)
+
+        where event is an MplEvent instance containing the following data:
+
+            name                # Event name.
+            canvas              # FigureCanvas instance generating the event.
+            x      = None       # x position - pixels from left of canvas.
+            y      = None       # y position - pixels from bottom of canvas.
+            button = None       # Button pressed: None, 1, 2, 3.
+            key    = None       # Key pressed: None, chr(range(255)), shift,
+                                  win, or control
+            inaxes = None       # Axes instance if cursor within axes.
+            xdata  = None       # x world coordinate.
+            ydata  = None       # y world coordinate.
+
+        For example:
+
+            def mouse_move(event):
+                print event.xdata, event.ydata
+
+            a = asaplot()
+            a.register('motion_notify', mouse_move)
+
+        If func is None, the event is deregistered.
+
+        Note that in TkAgg keyboard button presses don't generate an event.
+        """
+
+        if not self.events.has_key(type): return
+
+        if func is None:
+            if self.events[type] is not None:
+                # It's not clear that this does anything.
+                self.canvas.mpl_disconnect(self.events[type])
+                self.events[type] = None
+
+                # It seems to be necessary to return events to the toolbar.
+                if type == 'motion_notify':
+                    self.canvas.mpl_connect(type + '_event',
+                        self.figmgr.toolbar.mouse_move)
+                elif type == 'button_press':
+                    self.canvas.mpl_connect(type + '_event',
+                        self.figmgr.toolbar.press)
+                elif type == 'button_release':
+                    self.canvas.mpl_connect(type + '_event',
+                        self.figmgr.toolbar.release)
+
+        else:
+            self.events[type] = self.canvas.mpl_connect(type + '_event', func)
+
+
+    def release(self):
+        """
+        Release buffered graphics.
+        """
+        self.buffering = False
+        self.show()
+
+
+    def save(self, fname=None, orientation=None, dpi=None, papertype=None):
+        """
+        Save the plot to a file.
+
+        fname is the name of the output file.  The image format is determined
+        from the file suffix; 'png', 'ps', and 'eps' are recognized.  If no
+        file name is specified 'yyyymmdd_hhmmss.png' is created in the current
+        directory.
+        """
+        from asap import rcParams
+        if papertype is None:
+            papertype = rcParams['plotter.papertype']
+        if fname is None:
+            from datetime import datetime
+            dstr = datetime.now().strftime('%Y%m%d_%H%M%S')
+            fname = 'asap'+dstr+'.png'
+
+        d = ['png','.ps','eps']
+
+        from os.path import expandvars
+        fname = expandvars(fname)
+
+        if fname[-3:].lower() in d:
+            try:
+                if fname[-3:].lower() == ".ps":
+                    from matplotlib import __version__ as mv
+                    w = self.figure.figwidth.get()
+                    h = self.figure.figheight.get()
+
+                    if orientation is None:
+                        # oriented
+                        if w > h:
+                            orientation = 'landscape'
+                        else:
+                            orientation = 'portrait'
+                    from matplotlib.backends.backend_ps import papersize
+                    pw,ph = papersize[papertype.lower()]
+                    ds = None
+                    if orientation == 'landscape':
+                        ds = min(ph/w, pw/h)
+                    else:
+                        ds = min(pw/w, ph/h)
+                    ow = ds * w
+                    oh = ds * h
+                    self.figure.set_figsize_inches((ow, oh))
+                    self.figure.savefig(fname, orientation=orientation,
+                                        papertype=papertype.lower())
+                    self.figure.set_figsize_inches((w, h))
+                    print 'Written file %s' % (fname)
+                else:
+                    if dpi is None:
+                        dpi =150
+                    self.figure.savefig(fname,dpi=dpi)
+                    print 'Written file %s' % (fname)
+            except IOError, msg:
+                print 'Failed to save %s: Error msg was\n\n%s' % (fname, err)
+                return
+        else:
+            print "Invalid image type. Valid types are:"
+            print "'ps', 'eps', 'png'"
+
+
+    def set_axes(self, what=None, *args, **kwargs):
+        """
+        Set attributes for the axes by calling the relevant Axes.set_*()
+        method.  Colour translation is done as described in the doctext
+        for palette().
+        """
+
+        if what is None: return
+        if what[-6:] == 'colour': what = what[:-6] + 'color'
+
+        key = "colour"
+        if kwargs.has_key(key):
+            val = kwargs.pop(key)
+            kwargs["color"] = val
+
+        getattr(self.axes, "set_%s"%what)(*args, **kwargs)
+
+        self.show(hardrefresh=False)
+
+
+    def set_figure(self, what=None, *args, **kwargs):
+        """
+        Set attributes for the figure by calling the relevant Figure.set_*()
+        method.  Colour translation is done as described in the doctext
+        for palette().
+        """
+
+        if what is None: return
+        if what[-6:] == 'colour': what = what[:-6] + 'color'
+        #if what[-5:] == 'color' and len(args):
+        #    args = (get_colour(args[0]),)
+
+        newargs = {}
+        for k, v in kwargs.iteritems():
+            k = k.lower()
+            if k == 'colour': k = 'color'
+            newargs[k] = v
+
+        getattr(self.figure, "set_%s"%what)(*args, **newargs)
+        self.show(hardrefresh=False)
+
+
+    def set_limits(self, xlim=None, ylim=None):
+        """
+        Set x-, and y-limits for each subplot.
+
+        xlim = [xmin, xmax] as in axes.set_xlim().
+        ylim = [ymin, ymax] as in axes.set_ylim().
+        """
+        for s in self.subplots:
+            self.axes  = s['axes']
+            self.lines = s['lines']
+            oldxlim =  list(self.axes.get_xlim())
+            oldylim =  list(self.axes.get_ylim())
+            if xlim is not None:
+                for i in range(len(xlim)):
+                    if xlim[i] is not None:
+                        oldxlim[i] = xlim[i]
+            if ylim is not None:
+                for i in range(len(ylim)):
+                    if ylim[i] is not None:
+                        oldylim[i] = ylim[i]
+            self.axes.set_xlim(oldxlim)
+            self.axes.set_ylim(oldylim)
+        return
+
+
+    def set_line(self, number=None, **kwargs):
+        """
+        Set attributes for the specified line, or else the next line(s)
+        to be plotted.
+
+        number is the 0-relative number of a line that has already been
+        plotted.  If no such line exists, attributes are recorded and used
+        for the next line(s) to be plotted.
+
+        Keyword arguments specify Line2D attributes, e.g. color='r'.  Do
+
+            import matplotlib
+            help(matplotlib.lines)
+
+        The set_* methods of class Line2D define the attribute names and
+        values.  For non-US usage, "colour" is recognized as synonymous with
+        "color".
+
+        Set the value to None to delete an attribute.
+
+        Colour translation is done as described in the doctext for palette().
+        """
+
+        redraw = False
+        for k, v in kwargs.iteritems():
+            k = k.lower()
+            if k == 'colour': k = 'color'
+
+            if 0 <= number < len(self.lines):
+                if self.lines[number] is not None:
+                    for line in self.lines[number]:
+                        getattr(line, "set_%s"%k)(v)
+                    redraw = True
+            else:
+                if v is None:
+                    del self.attributes[k]
+                else:
+                    self.attributes[k] = v
+
+        if redraw: self.show(hardrefresh=False)
+
+
+    def set_panels(self, rows=1, cols=0, n=-1, nplots=-1, ganged=True):
+        """
+        Set the panel layout.
+
+        rows and cols, if cols != 0, specify the number of rows and columns in
+        a regular layout.   (Indexing of these panels in matplotlib is row-
+        major, i.e. column varies fastest.)
+
+        cols == 0 is interpreted as a retangular layout that accomodates
+        'rows' panels, e.g. rows == 6, cols == 0 is equivalent to
+        rows == 2, cols == 3.
+
+        0 <= n < rows*cols is interpreted as the 0-relative panel number in
+        the configuration specified by rows and cols to be added to the
+        current figure as its next 0-relative panel number (i).  This allows
+        non-regular panel layouts to be constructed via multiple calls.  Any
+        other value of n clears the plot and produces a rectangular array of
+        empty panels.  The number of these may be limited by nplots.
+        """
+        if n < 0 and len(self.subplots):
+            self.figure.clear()
+            self.set_title()
+
+        if rows < 1: rows = 1
+
+        if cols <= 0:
+            i = int(sqrt(rows))
+            if i*i < rows: i += 1
+            cols = i
+
+            if i*(i-1) >= rows: i -= 1
+            rows = i
+
+        if 0 <= n < rows*cols:
+            i = len(self.subplots)
+            self.subplots.append({})
+
+            self.subplots[i]['axes']  = self.figure.add_subplot(rows,
+                                            cols, n+1)
+            self.subplots[i]['lines'] = []
+
+            if i == 0: self.subplot(0)
+
+            self.rows = 0
+            self.cols = 0
+
+        else:
+            self.subplots = []
+
+            if nplots < 1 or rows*cols < nplots:
+                nplots = rows*cols
+            if ganged:
+                hsp,wsp = None,None
+                if rows > 1: hsp = 0.0001
+                if cols > 1: wsp = 0.0001
+                self.figure.subplots_adjust(wspace=wsp,hspace=hsp)
+            for i in range(nplots):
+                self.subplots.append({})
+                self.subplots[i]['lines'] = []
+                if not ganged:
+                    self.subplots[i]['axes'] = self.figure.add_subplot(rows,
+                                                cols, i+1)
+                else:
+                    if i == 0:
+                        self.subplots[i]['axes'] = self.figure.add_subplot(rows,
+                                                cols, i+1)
+                    else:
+                        self.subplots[i]['axes'] = self.figure.add_subplot(rows,
+                                                cols, i+1,
+                                                sharex=self.subplots[0]['axes'],
+                                                sharey=self.subplots[0]['axes'])
+                    # Suppress tick labelling for interior subplots.
+                    if i <= (rows-1)*cols - 1:
+                        if i+cols < nplots:
+                            # Suppress x-labels for frames width
+                            # adjacent frames
+                            for tick in self.subplots[i]['axes'].xaxis.majorTicks:
+                                tick.label1On = False
+                            self.subplots[i]['axes'].xaxis.label.set_visible(False)
+                    if i%cols:
+                        # Suppress y-labels for frames not in the left column.
+                        for tick in self.subplots[i]['axes'].yaxis.majorTicks:
+                            tick.label1On = False
+                        self.subplots[i]['axes'].yaxis.label.set_visible(False)
+                    # disable the first tick of [1:ncol-1] of the last row
+                    #if i+1 < nplots:
+                    #    self.subplots[i]['axes'].xaxis.majorTicks[0].label1On = False
+                self.rows = rows
+                self.cols = cols
+            self.subplot(0)
+
+    def tidy(self):
+        # this needs to be exceuted after the first "refresh"
+        nplots = len(self.subplots)
+        if nplots == 1: return
+        for i in xrange(nplots):
+            ax = self.subplots[i]['axes']
+            if i%self.cols:
+                ax.xaxis.majorTicks[0].label1On = False
+            else:
+                if i != 0:
+                    ax.yaxis.majorTicks[-1].label1On = False
+
+
+    def set_title(self, title=None):
+        """
+        Set the title of the plot window.  Use the previous title if title is
+        omitted.
+        """
+        if title is not None:
+            self.title = title
+
+        self.figure.text(0.5, 0.95, self.title, horizontalalignment='center')
+
+
+    def show(self, hardrefresh=True):
+        """
+        Show graphics dependent on the current buffering state.
+        """
+        if not hardrefresh: return
+        if not self.buffering:
+            if self.loc is not None:
+                for sp in self.subplots:
+                    lines  = []
+                    labels = []
+                    i = 0
+                    for line in sp['lines']:
+                        i += 1
+                        if line is not None:
+                            lines.append(line[0])
+                            lbl = line[0].get_label()
+                            if lbl == '':
+                                lbl = str(i)
+                            labels.append(lbl)
+
+                    if len(lines):
+                        fp = FP(size=rcParams['legend.fontsize'])
+                        fsz = fp.get_size_in_points() - len(lines)
+                        fp.set_size(max(fsz,6))
+                        sp['axes'].legend(tuple(lines), tuple(labels),
+                                          self.loc, prop=fp)
+                    else:
+                        sp['axes'].legend((' '))
+
+            from matplotlib.artist import setp
+            fp = FP(size=rcParams['xtick.labelsize'])
+            xts = fp.get_size_in_points()- (self.cols)/2
+            fp = FP(size=rcParams['ytick.labelsize'])
+            yts = fp.get_size_in_points() - (self.rows)/2
+            for sp in self.subplots:
+                ax = sp['axes']
+                s = ax.title.get_size()
+                tsize = s-(self.cols+self.rows)
+                ax.title.set_size(tsize)
+                fp = FP(size=rcParams['axes.labelsize'])
+                setp(ax.get_xticklabels(), fontsize=xts)
+                setp(ax.get_yticklabels(), fontsize=yts)
+                origx =  fp.get_size_in_points()
+                origy = origx
+                off = 0
+                if self.cols > 1: off = self.cols
+                xfsize = origx-off
+                ax.xaxis.label.set_size(xfsize)
+                off = 0
+                if self.rows > 1: off = self.rows
+                yfsize = origy-off
+                ax.yaxis.label.set_size(yfsize)
+
+    def subplot(self, i=None, inc=None):
+        """
+        Set the subplot to the 0-relative panel number as defined by one or
+        more invokations of set_panels().
+        """
+        l = len(self.subplots)
+        if l:
+            if i is not None:
+                self.i = i
+
+            if inc is not None:
+                self.i += inc
+
+            self.i %= l
+            self.axes  = self.subplots[self.i]['axes']
+            self.lines = self.subplots[self.i]['lines']
+
+    def text(self, *args, **kwargs):
+        """
+        Add text to the figure.
+        """
+        self.figure.text(*args, **kwargs)
+        self.show()
+
+    def vline_with_label(self, x, y, label,
+                         location='bottom', rotate=0.0, **kwargs):
+        """
+        Plot a vertical line with label.
+        It takes "world" values fo x and y.
+        """
+        ax = self.axes
+        # need this to suppress autoscaling during this function
+        self.axes.set_autoscale_on(False)
+        ymin = 0.0
+        ymax = 1.0
+        valign = 'center'
+        if location.lower() == 'top':
+            y = max(0.0, y)
+        elif location.lower() == 'bottom':
+            y = min(0.0, y)
+        lbloffset = 0.06
+        # a rough estimate for the bb of the text
+        if rotate > 0.0: lbloffset = 0.03*len(label)
+        peakoffset = 0.01
+        xy0 = ax.transData.xy_tup((x,y))
+        # get relative coords
+        xy = ax.transAxes.inverse_xy_tup(xy0)
+        if location.lower() == 'top':
+            ymax = 1.0-lbloffset
+            ymin = xy[1]+peakoffset
+            valign = 'bottom'
+            ylbl = ymax+0.01
+        elif location.lower() == 'bottom':
+            ymin = lbloffset
+            ymax = xy[1]-peakoffset
+            valign = 'top'
+            ylbl = ymin-0.01
+        trans = blend_xy_sep_transform(ax.transData, ax.transAxes)
+        l = ax.axvline(x, ymin, ymax, color='black', **kwargs)
+        t = ax.text(x, ylbl ,label, verticalalignment=valign,
+                                    horizontalalignment='center',
+                    rotation=rotate,transform = trans)
+        self.axes.set_autoscale_on(True)
Index: tags/Release2.1.0b/python/asaplotgui.py
===================================================================
--- tags/Release2.1.0b/python/asaplotgui.py	(revision 1196)
+++ tags/Release2.1.0b/python/asaplotgui.py	(revision 1196)
@@ -0,0 +1,199 @@
+"""
+ASAP plotting class based on matplotlib.
+"""
+
+from asap.asaplotbase import *
+import Tkinter as Tk
+from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, \
+	FigureManagerTkAgg
+# Force use of the newfangled toolbar.
+import matplotlib
+matplotlib.use("TkAgg")
+matplotlib.rcParams['toolbar'] = 'toolbar2'
+
+class asaplotgui(asaplotbase):
+    """
+    ASAP plotting class based on matplotlib.
+    """
+
+    def __init__(self, rows=1, cols=0, title='', size=(8,6), buffering=False):
+	"""
+	Create a new instance of the ASAPlot plotting class.
+
+	If rows < 1 then a separate call to set_panels() is required to define
+	the panel layout; refer to the doctext for set_panels().
+	"""
+        v = vars()
+        del v['self']
+
+        asaplotbase.__init__(self, **v)
+        self.window = Tk.Tk()
+        def dest_callback():
+            self.is_dead = True
+            self.window.destroy()
+
+        self.window.protocol("WM_DELETE_WINDOW", dest_callback)
+        self.canvas = FigureCanvasTkAgg(self.figure, master=self.window)
+        self.canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
+        # Simply instantiating this is enough to get a working toolbar.
+        self.figmgr = FigureManagerTkAgg(self.canvas, 1, self.window)
+        self.window.wm_title('ASAP Plotter - Tk')
+
+	self.events = {'button_press':None,
+		       'button_release':None,
+		       'motion_notify':None}
+
+	matplotlib.rcParams["interactive"] = True
+	#self.buffering = buffering
+
+	self.canvas.show()
+
+    def map(self):
+	"""
+	Reveal the ASAPlot graphics window and bring it to the top of the
+	window stack.
+	"""
+	self.window.wm_deiconify()
+	self.window.lift()
+
+    def position(self):
+	"""
+	Use the mouse to get a position from a graph.
+	"""
+
+	def position_disable(event):
+	    self.register('button_press', None)
+	    print '%.4f, %.4f' % (event.xdata, event.ydata)
+
+	print 'Press any mouse button...'
+	self.register('button_press', position_disable)
+
+
+    def quit(self):
+	"""
+	Destroy the ASAPlot graphics window.
+	"""
+	self.window.destroy()
+
+
+    def region(self):
+	"""
+	Use the mouse to get a rectangular region from a plot.
+
+	The return value is [x0, y0, x1, y1] in world coordinates.
+	"""
+
+	def region_start(event):
+	    height = self.canvas.figure.bbox.height()
+	    self.rect = {'fig': None, 'height': height,
+			 'x': event.x, 'y': height - event.y,
+			 'world': [event.xdata, event.ydata,
+				   event.xdata, event.ydata]}
+	    self.register('button_press', None)
+	    self.register('motion_notify', region_draw)
+	    self.register('button_release', region_disable)
+
+	def region_draw(event):
+	    self.canvas._tkcanvas.delete(self.rect['fig'])
+	    self.rect['fig'] = self.canvas._tkcanvas.create_rectangle(
+				self.rect['x'], self.rect['y'],
+				event.x, self.rect['height'] - event.y)
+
+	def region_disable(event):
+	    self.register('motion_notify', None)
+	    self.register('button_release', None)
+
+	    self.canvas._tkcanvas.delete(self.rect['fig'])
+
+	    self.rect['world'][2:4] = [event.xdata, event.ydata]
+	    print '(%.2f, %.2f)  (%.2f, %.2f)' % (self.rect['world'][0],
+		self.rect['world'][1], self.rect['world'][2],
+		self.rect['world'][3])
+
+	self.register('button_press', region_start)
+
+	# This has to be modified to block and return the result (currently
+	# printed by region_disable) when that becomes possible in matplotlib.
+
+	return [0.0, 0.0, 0.0, 0.0]
+
+
+    def register(self, type=None, func=None):
+	"""
+	Register, reregister, or deregister events of type 'button_press',
+	'button_release', or 'motion_notify'.
+
+	The specified callback function should have the following signature:
+
+	    def func(event)
+
+	where event is an MplEvent instance containing the following data:
+
+	    name		# Event name.
+	    canvas		# FigureCanvas instance generating the event.
+	    x      = None	# x position - pixels from left of canvas.
+	    y      = None	# y position - pixels from bottom of canvas.
+	    button = None	# Button pressed: None, 1, 2, 3.
+	    key    = None	# Key pressed: None, chr(range(255)), shift,
+				  win, or control
+	    inaxes = None	# Axes instance if cursor within axes.
+	    xdata  = None	# x world coordinate.
+	    ydata  = None	# y world coordinate.
+
+	For example:
+
+	    def mouse_move(event):
+		print event.xdata, event.ydata
+
+	    a = asaplot()
+	    a.register('motion_notify', mouse_move)
+
+	If func is None, the event is deregistered.
+
+	Note that in TkAgg keyboard button presses don't generate an event.
+	"""
+
+	if not self.events.has_key(type): return
+
+	if func is None:
+	    if self.events[type] is not None:
+		# It's not clear that this does anything.
+		self.canvas.mpl_disconnect(self.events[type])
+		self.events[type] = None
+
+		# It seems to be necessary to return events to the toolbar.
+		if type == 'motion_notify':
+		    self.canvas.mpl_connect(type + '_event',
+			self.figmgr.toolbar.mouse_move)
+		elif type == 'button_press':
+		    self.canvas.mpl_connect(type + '_event',
+			self.figmgr.toolbar.press)
+		elif type == 'button_release':
+		    self.canvas.mpl_connect(type + '_event',
+			self.figmgr.toolbar.release)
+
+	else:
+	    self.events[type] = self.canvas.mpl_connect(type + '_event', func)
+
+
+    def show(self, hardrefresh=True):
+	"""
+	Show graphics dependent on the current buffering state.
+	"""
+	if not self.buffering:
+            if hardrefresh:
+                asaplotbase.show(self)
+	    self.window.wm_deiconify()
+	    self.canvas.show()
+
+    def terminate(self):
+	"""
+	Clear the figure.
+	"""
+	self.window.destroy()
+
+    def unmap(self):
+	"""
+	Hide the ASAPlot graphics window.
+	"""
+	self.window.wm_withdraw()
Index: tags/Release2.1.0b/python/asaplotgui_gtk.py
===================================================================
--- tags/Release2.1.0b/python/asaplotgui_gtk.py	(revision 1196)
+++ tags/Release2.1.0b/python/asaplotgui_gtk.py	(revision 1196)
@@ -0,0 +1,200 @@
+"""
+ASAP plotting class based on matplotlib.
+"""
+
+from asap.asaplotbase import *
+# Force use of the newfangled toolbar.
+import gtk
+import matplotlib
+from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
+from matplotlib.backends.backend_gtkagg import FigureManagerGTKAgg
+matplotlib.use("GTkAgg")
+matplotlib.rcParams['toolbar'] = 'toolbar2'
+from matplotlib.backends.backend_gtk import NavigationToolbar2GTK as NavigationToolbar
+
+class asaplotgui(asaplotbase):
+    """
+    ASAP plotting class based on matplotlib.
+    """
+
+    def __init__(self, rows=1, cols=0, title='', size=(8,6), buffering=False):
+        """
+        Create a new instance of the ASAPlot plotting class.
+
+        If rows < 1 then a separate call to set_panels() is required to define
+        the panel layout; refer to the doctext for set_panels().
+        """
+        v = vars()
+        del v['self']
+
+        asaplotbase.__init__(self, **v)
+        matplotlib.interactive = True
+        self.canvas = FigureCanvas(self.figure)
+        # Simply instantiating this is enough to get a working toolbar.
+        self.figmgr = FigureManagerGTKAgg(self.canvas, 1)
+        def dest_callback(val):
+            self.is_dead = True
+            self.figmgr.window.destroy()
+        self.window = self.figmgr.window
+        self.window.connect("destroy", dest_callback )
+        self.window.set_title('ASAP Plotter - GTK')
+        self.events = {'button_press':None,
+                       'button_release':None,
+                       'motion_notify':None}
+
+        self.buffering = buffering
+        matplotlib.rcParams['interactive'] = True
+        #self.canvas.set_size_request(800,600)
+
+        #self.canvas.show()
+
+    def map(self):
+        """
+        Reveal the ASAPlot graphics window and bring it to the top of the
+        window stack.
+        """
+        self.window.deiconify()
+        #self.window.lift()
+
+    def position(self):
+        """
+        Use the mouse to get a position from a graph.
+        """
+
+        def position_disable(event):
+            self.register('button_press', None)
+            print '%.4f, %.4f' % (event.xdata, event.ydata)
+
+        print 'Press any mouse button...'
+        self.register('button_press', position_disable)
+
+
+    def quit(self):
+        """
+        Destroy the ASAPlot graphics window.
+        """
+        self.window.destroy()
+
+
+    def region(self):
+        """
+        Use the mouse to get a rectangular region from a plot.
+
+        The return value is [x0, y0, x1, y1] in world coordinates.
+        """
+
+        def region_start(event):
+            height = self.canvas.figure.bbox.height()
+            self.rect = {'fig': None, 'height': height,
+                         'x': event.x, 'y': height - event.y,
+                         'world': [event.xdata, event.ydata,
+                                   event.xdata, event.ydata]}
+            self.register('button_press', None)
+            self.register('motion_notify', region_draw)
+            self.register('button_release', region_disable)
+
+        def region_draw(event):
+            self.canvas._tkcanvas.delete(self.rect['fig'])
+            self.rect['fig'] = self.canvas._tkcanvas.create_rectangle(
+                                self.rect['x'], self.rect['y'],
+                                event.x, self.rect['height'] - event.y)
+
+        def region_disable(event):
+            self.register('motion_notify', None)
+            self.register('button_release', None)
+
+            self.canvas._tkcanvas.delete(self.rect['fig'])
+
+            self.rect['world'][2:4] = [event.xdata, event.ydata]
+            print '(%.2f, %.2f)  (%.2f, %.2f)' % (self.rect['world'][0],
+                self.rect['world'][1], self.rect['world'][2],
+                self.rect['world'][3])
+
+        self.register('button_press', region_start)
+
+        # This has to be modified to block and return the result (currently
+        # printed by region_disable) when that becomes possible in matplotlib.
+
+        return [0.0, 0.0, 0.0, 0.0]
+
+
+    def register(self, type=None, func=None):
+        """
+        Register, reregister, or deregister events of type 'button_press',
+        'button_release', or 'motion_notify'.
+
+        The specified callback function should have the following signature:
+
+            def func(event)
+
+        where event is an MplEvent instance containing the following data:
+
+            name                # Event name.
+            canvas              # FigureCanvas instance generating the event.
+            x      = None       # x position - pixels from left of canvas.
+            y      = None       # y position - pixels from bottom of canvas.
+            button = None       # Button pressed: None, 1, 2, 3.
+            key    = None       # Key pressed: None, chr(range(255)), shift,
+                                  win, or control
+            inaxes = None       # Axes instance if cursor within axes.
+            xdata  = None       # x world coordinate.
+            ydata  = None       # y world coordinate.
+
+        For example:
+
+            def mouse_move(event):
+                print event.xdata, event.ydata
+
+            a = asaplot()
+            a.register('motion_notify', mouse_move)
+
+        If func is None, the event is deregistered.
+
+        Note that in TkAgg keyboard button presses don't generate an event.
+        """
+
+        if not self.events.has_key(type): return
+
+        if func is None:
+            if self.events[type] is not None:
+                # It's not clear that this does anything.
+                self.canvas.mpl_disconnect(self.events[type])
+                self.events[type] = None
+
+                # It seems to be necessary to return events to the toolbar.
+                if type == 'motion_notify':
+                    self.canvas.mpl_connect(type + '_event',
+                        self.figmgr.toolbar.mouse_move)
+                elif type == 'button_press':
+                    self.canvas.mpl_connect(type + '_event',
+                        self.figmgr.toolbar.press)
+                elif type == 'button_release':
+                    self.canvas.mpl_connect(type + '_event',
+                        self.figmgr.toolbar.release)
+
+        else:
+            self.events[type] = self.canvas.mpl_connect(type + '_event', func)
+
+
+    def show(self, hardrefresh=True):
+        """
+        Show graphics dependent on the current buffering state.
+        """
+        if not self.buffering:
+            if hardrefresh:
+                asaplotbase.show(self, hardrefresh)
+            self.window.deiconify()
+            self.canvas.draw()
+            self.window.show_all()
+
+    def terminate(self):
+        """
+        Clear the figure.
+        """
+        self.window.destroy()
+
+    def unmap(self):
+        """
+        Hide the ASAPlot graphics window.
+        """
+        self.window.wm_withdraw()
Index: tags/Release2.1.0b/python/asapmath.py
===================================================================
--- tags/Release2.1.0b/python/asapmath.py	(revision 1196)
+++ tags/Release2.1.0b/python/asapmath.py	(revision 1196)
@@ -0,0 +1,154 @@
+from asap.scantable import scantable
+from asap import rcParams
+from asap import print_log
+
+def average_time(*args, **kwargs):
+    """
+    Return the (time) average of a scan or list of scans. [in channels only]
+    The cursor of the output scan is set to 0
+    Parameters:
+        one scan or comma separated  scans
+        mask:     an optional mask (only used for 'var' and 'tsys' weighting)
+        scanav:   True averages each scan separately.
+                  False (default) averages all scans together,
+        weight:   Weighting scheme. 'none, 'var' (1/var(spec)
+                  weighted), 'tsys' (1/Tsys**2 weighted), 'tint'
+                  (integration time weighted) or 'tintsys' (Tsys
+                  and tint). The default is 'tint'
+        align:    align the spectra in velocity before averaging. It takes
+                  the time of the first spectrum in the first scantable
+                  as reference time.
+    Example:
+        # return a time averaged scan from scana and scanb
+        # without using a mask
+        scanav = average_time(scana,scanb)
+        # return the (time) averaged scan, i.e. the average of
+        # all correlator cycles
+        scanav = average_time(scan, scanav=True)
+    """
+    scanav = False
+    if kwargs.has_key('scanav'):
+       scanav = kwargs.get('scanav')
+    weight = 'tint'
+    if kwargs.has_key('weight'):
+       weight = kwargs.get('weight')
+    mask = ()
+    if kwargs.has_key('mask'):
+        mask = kwargs.get('mask')
+    align = False
+    if kwargs.has_key('align'):
+        align = kwargs.get('align')
+    varlist = vars()
+    if isinstance(args[0],list):
+        lst = args[0]
+    elif isinstance(args[0],tuple):
+        lst = list(args[0])
+    else:
+        lst = list(args)
+
+    del varlist["kwargs"]
+    varlist["args"] = "%d scantables" % len(lst)
+    # need special formatting here for history...
+
+    from asap._asap import stmath
+    stm = stmath()
+    for s in lst:
+        if not isinstance(s,scantable):
+            msg = "Please give a list of scantables"
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise TypeError(msg)
+    if scanav: scanav = "SCAN"
+    else: scanav = "NONE"
+    alignedlst = []
+    if align:
+        refepoch = lst[0].get_time(0)
+        for scan in lst:
+            alignedlst.append(scan.freq_align(refepoch,insitu=False))
+    else:
+        alignedlst = lst
+    s = scantable(stm._average(alignedlst, mask, weight.upper(), scanav))
+    s._add_history("average_time",varlist)
+    print_log()
+    return s
+
+def quotient(source, reference, preserve=True):
+    """
+    Return the quotient of a 'source' (signal) scan and a 'reference' scan.
+    The reference can have just one scan, even if the signal has many. Otherwise
+    they must have the same number of scans.
+    The cursor of the output scan is set to 0
+    Parameters:
+        source:        the 'on' scan
+        reference:     the 'off' scan
+        preserve:      you can preserve (default) the continuum or
+                       remove it.  The equations used are
+                       preserve:  Output = Toff * (on/off) - Toff
+                       remove:    Output = Toff * (on/off) - Ton
+    """
+    varlist = vars()
+    from asap._asap import stmath
+    stm = stmath()
+    stm._setinsitu(False)
+    s = scantable(stm._quotient(source, reference, preserve))
+    s._add_history("quotient",varlist)
+    print_log()
+    return s
+
+def simple_math(left, right, op='add', tsys=True):
+    """
+    Apply simple mathematical binary operations to two
+    scan tables,  returning the result in a new scan table.
+    The operation is applied to both the correlations and the TSys data
+    The cursor of the output scan is set to 0
+    Parameters:
+        left:          the 'left' scan
+        right:         the 'right' scan
+        op:            the operation: 'add' (default), 'sub', 'mul', 'div'
+        tsys:          if True (default) then apply the operation to Tsys
+                       as well as the data
+    """
+    varlist = vars()
+    print "Not yet available in asap"
+    return
+    if not isinstance(left,scantable) and not isinstance(right,scantable):
+        msg = "Please provide two scantables as input"
+        if rcParams['verbose']:
+            print msg
+            return
+        else:
+            raise TypeError(msg)
+    s = scantable(stm._bop(left, right, op, tsys))
+    s._add_history("simple_math", varlist)
+    print_log()
+    return s
+
+def merge(*args):
+    """
+    """
+    varlist = vars()
+    if isinstance(args[0],list):
+        lst = tuple(args[0])
+    elif isinstance(args[0],tuple):
+        lst = args[0]
+    else:
+        lst = tuple(args)
+    varlist["args"] = "%d scantables" % len(lst)
+    # need special formatting her for history...
+    from asap._asap import stmath
+    stm = stmath()
+    for s in lst:
+        if not isinstance(s,scantable):
+            msg = "Please give a list of scantables"
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise TypeError(msg)
+    s = scantable(stm._merge(lst))
+    s._add_history("merge", varlist)
+    print_log()
+    return s
+
Index: tags/Release2.1.0b/python/asapplotter.py
===================================================================
--- tags/Release2.1.0b/python/asapplotter.py	(revision 1196)
+++ tags/Release2.1.0b/python/asapplotter.py	(revision 1196)
@@ -0,0 +1,735 @@
+from asap import rcParams, print_log, selector
+from asap import NUM
+import matplotlib.axes
+
+class asapplotter:
+    """
+    The ASAP plotter.
+    By default the plotter is set up to plot polarisations
+    'colour stacked' and scantables across panels.
+    Note:
+        Currenly it only plots 'spectra' not Tsys or
+        other variables.
+    """
+    def __init__(self, visible=None):
+        self._visible = rcParams['plotter.gui']
+        if visible is not None:
+            self._visible = visible
+        self._plotter = self._newplotter()
+
+        self._panelling = None
+        self._stacking = None
+        self.set_panelling()
+        self.set_stacking()
+        self._rows = None
+        self._cols = None
+        self._autoplot = False
+        self._minmaxx = None
+        self._minmaxy = None
+        self._datamask = None
+        self._data = None
+        self._lmap = None
+        self._title = None
+        self._ordinate = None
+        self._abcissa = None
+        self._abcunit = None
+        self._usermask = []
+        self._maskselection = None
+        self._selection = selector()
+        self._hist = rcParams['plotter.histogram']
+
+    def _translate(self, instr):
+        keys = "s b i p t".split()
+        if isinstance(instr, str):
+            for key in keys:
+                if instr.lower().startswith(key):
+                    return key
+        return None
+
+    def _newplotter(self):
+        if self._visible:
+            from asap.asaplotgui import asaplotgui as asaplot
+        else:
+            from asap.asaplot import asaplot
+        return asaplot()
+
+
+    def plot(self, scan=None):
+        """
+        Plot a scantable.
+        Parameters:
+            scan:   a scantable
+        Note:
+            If a scantable was specified in a previous call
+            to plot, no argument has to be given to 'replot'
+            NO checking is done that the abcissas of the scantable
+            are consistent e.g. all 'channel' or all 'velocity' etc.
+        """
+        if self._plotter.is_dead:
+            self._plotter = self._newplotter()
+        self._plotter.hold()
+        self._plotter.clear()
+        from asap import scantable
+        if not self._data and not scan:
+            msg = "Input is not a scantable"
+            if rcParams['verbose']:
+                print msg
+                return
+            raise TypeError(msg)
+        if isinstance(scan, scantable):
+            if self._data is not None:
+                if scan != self._data:
+                    self._data = scan
+                    # reset
+                    self._reset()
+            else:
+                self._data = scan
+                self._reset()
+        # ranges become invalid when unit changes
+        if self._abcunit and self._abcunit != self._data.get_unit():
+            self._minmaxx = None
+            self._minmaxy = None
+            self._abcunit = self._data.get_unit()
+            self._datamask = None
+        self._plot(self._data)
+        if self._minmaxy is not None:
+            self._plotter.set_limits(ylim=self._minmaxy)
+        self._plotter.release()
+        self._plotter.tidy()
+        self._plotter.show(hardrefresh=False)
+        print_log()
+        return
+
+
+    # forwards to matplotlib axes
+    def text(self, *args, **kwargs):
+        self._axes_callback("text", *args, **kwargs)
+    text. __doc__ = matplotlib.axes.Axes.text.__doc__
+    def arrow(self, *args, **kwargs):
+        self._axes_callback("arrow", *args, **kwargs)
+    arrow. __doc__ = matplotlib.axes.Axes.arrow.__doc__
+    def axvline(self, *args, **kwargs):
+        self._axes_callback("axvline", *args, **kwargs)
+    axvline. __doc__ = matplotlib.axes.Axes.axvline.__doc__
+    def axhline(self, *args, **kwargs):
+        self._axes_callback("axhline", *args, **kwargs)
+    axhline. __doc__ = matplotlib.axes.Axes.axhline.__doc__
+    def axvspan(self, *args, **kwargs):
+        self._axes_callback("axvspan", *args, **kwargs)
+        # hack to preventy mpl from redrawing the patch
+        # it seem to convert the patch into lines on every draw.
+        # This doesn't happen in a test script???
+        del self._plotter.axes.patches[-1]
+    axvspan. __doc__ = matplotlib.axes.Axes.axvspan.__doc__
+    def axhspan(self, *args, **kwargs):
+        self._axes_callback("ahvspan", *args, **kwargs)
+        # hack to preventy mpl from redrawing the patch
+        # it seem to convert the patch into lines on every draw.
+        # This doesn't happen in a test script???
+        del self._plotter.axes.patches[-1]
+    axhspan. __doc__ = matplotlib.axes.Axes.axhspan.__doc__
+
+    def _axes_callback(self, axesfunc, *args, **kwargs):
+        panel = 0
+        if kwargs.has_key("panel"):
+            panel = kwargs.pop("panel")
+        coords = None
+        if kwargs.has_key("coords"):
+            coords = kwargs.pop("coords")
+            if coords.lower() == 'world':
+                kwargs["transform"] = self._plotter.axes.transData
+            elif coords.lower() == 'relative':
+                kwargs["transform"] = self._plotter.axes.transAxes
+        self._plotter.subplot(panel)
+        self._plotter.axes.set_autoscale_on(False)
+        getattr(self._plotter.axes, axesfunc)(*args, **kwargs)
+        self._plotter.show(False)
+        self._plotter.axes.set_autoscale_on(True)
+    # end matplotlib.axes fowarding functions
+
+    def set_mode(self, stacking=None, panelling=None):
+        """
+        Set the plots look and feel, i.e. what you want to see on the plot.
+        Parameters:
+            stacking:     tell the plotter which variable to plot
+                          as line color overlays (default 'pol')
+            panelling:    tell the plotter which variable to plot
+                          across multiple panels (default 'scan'
+        Note:
+            Valid modes are:
+                 'beam' 'Beam' 'b':     Beams
+                 'if' 'IF' 'i':         IFs
+                 'pol' 'Pol' 'p':       Polarisations
+                 'scan' 'Scan' 's':     Scans
+                 'time' 'Time' 't':     Times
+        """
+        msg = "Invalid mode"
+        if not self.set_panelling(panelling) or \
+               not self.set_stacking(stacking):
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise TypeError(msg)
+        if self._data: self.plot(self._data)
+        return
+
+    def set_panelling(self, what=None):
+        mode = what
+        if mode is None:
+             mode = rcParams['plotter.panelling']
+        md = self._translate(mode)
+        if md:
+            self._panelling = md
+            self._title = None
+            return True
+        return False
+
+    def set_layout(self,rows=None,cols=None):
+        """
+        Set the multi-panel layout, i.e. how many rows and columns plots
+        are visible.
+        Parameters:
+             rows:   The number of rows of plots
+             cols:   The number of columns of plots
+        Note:
+             If no argument is given, the potter reverts to its auto-plot
+             behaviour.
+        """
+        self._rows = rows
+        self._cols = cols
+        if self._data: self.plot(self._data)
+        return
+
+    def set_stacking(self, what=None):
+        mode = what
+        if mode is None:
+             mode = rcParams['plotter.stacking']
+        md = self._translate(mode)
+        if md:
+            self._stacking = md
+            self._lmap = None
+            return True
+        return False
+
+    def set_range(self,xstart=None,xend=None,ystart=None,yend=None):
+        """
+        Set the range of interest on the abcissa of the plot
+        Parameters:
+            [x,y]start,[x,y]end:  The start and end points of the 'zoom' window
+        Note:
+            These become non-sensical when the unit changes.
+            use plotter.set_range() without parameters to reset
+
+        """
+        if xstart is None and xend is None:
+            self._minmaxx = None
+        else:
+            self._minmaxx = [xstart,xend]
+        if ystart is None and yend is None:
+            self._minmaxy = None
+        else:
+            self._minmaxy = [ystart,yend]
+        if self._data: self.plot(self._data)
+        return
+
+    def set_legend(self, mp=None, fontsize = None, mode = 0):
+        """
+        Specify a mapping for the legend instead of using the default
+        indices:
+        Parameters:
+            mp:        a list of 'strings'. This should have the same length
+                       as the number of elements on the legend and then maps
+                       to the indeces in order. It is possible to uses latex
+                       math expression. These have to be enclosed in r'',
+                       e.g. r'$x^{2}$'
+            fontsize:  The font size of the label (default None)
+            mode:      where to display the legend
+                       Any other value for loc else disables the legend:
+                        0: auto
+                        1: upper right
+                        2: upper left
+                        3: lower left
+                        4: lower right
+                        5: right
+                        6: center left
+                        7: center right
+                        8: lower center
+                        9: upper center
+                        10: center
+
+        Example:
+             If the data has two IFs/rest frequencies with index 0 and 1
+             for CO and SiO:
+             plotter.set_stacking('i')
+             plotter.set_legend(['CO','SiO'])
+             plotter.plot()
+             plotter.set_legend([r'$^{12}CO$', r'SiO'])
+        """
+        self._lmap = mp
+        self._plotter.legend(mode)
+        if isinstance(fontsize, int):
+            from matplotlib import rc as rcp
+            rcp('legend', fontsize=fontsize)
+        if self._data:
+            self.plot(self._data)
+        return
+
+    def set_title(self, title=None, fontsize=None):
+        """
+        Set the title of the plot. If multiple panels are plotted,
+        multiple titles have to be specified.
+        Example:
+             # two panels are visible on the plotter
+             plotter.set_title(["First Panel","Second Panel"])
+        """
+        self._title = title
+        if isinstance(fontsize, int):
+            from matplotlib import rc as rcp
+            rcp('axes', titlesize=fontsize)
+        if self._data: self.plot(self._data)
+        return
+
+    def set_ordinate(self, ordinate=None, fontsize=None):
+        """
+        Set the y-axis label of the plot. If multiple panels are plotted,
+        multiple labels have to be specified.
+        Parameters:
+            ordinate:    a list of ordinate labels. None (default) let
+                         data determine the labels
+        Example:
+             # two panels are visible on the plotter
+             plotter.set_ordinate(["First Y-Axis","Second Y-Axis"])
+        """
+        self._ordinate = ordinate
+        if isinstance(fontsize, int):
+            from matplotlib import rc as rcp
+            rcp('axes', labelsize=fontsize)
+            rcp('ytick', labelsize=fontsize)
+        if self._data: self.plot(self._data)
+        return
+
+    def set_abcissa(self, abcissa=None, fontsize=None):
+        """
+        Set the x-axis label of the plot. If multiple panels are plotted,
+        multiple labels have to be specified.
+        Parameters:
+            abcissa:     a list of abcissa labels. None (default) let
+                         data determine the labels
+        Example:
+             # two panels are visible on the plotter
+             plotter.set_ordinate(["First X-Axis","Second X-Axis"])
+        """
+        self._abcissa = abcissa
+        if isinstance(fontsize, int):
+            from matplotlib import rc as rcp
+            rcp('axes', labelsize=fontsize)
+            rcp('xtick', labelsize=fontsize)
+        if self._data: self.plot(self._data)
+        return
+
+    def set_colors(self, colormap):
+        """
+        Set the colors to be used. The plotter will cycle through
+        these colors when lines are overlaid (stacking mode).
+        Parameters:
+            colormap:     a list of colour names
+        Example:
+             plotter.set_colors("red green blue")
+             # If for example four lines are overlaid e.g I Q U V
+             # 'I' will be 'red', 'Q' will be 'green', U will be 'blue'
+             # and 'V' will be 'red' again.
+        """
+        if isinstance(colormap,str):
+            colormap = colormap.split()
+        self._plotter.palette(0,colormap=colormap)
+        if self._data: self.plot(self._data)
+
+    def set_histogram(self, hist=True, linewidth=None):
+        """
+        Enable/Disable histogram-like plotting.
+        Parameters:
+            hist:        True (default) or False. The fisrt default
+                         is taken from the .asaprc setting
+                         plotter.histogram
+        """
+        self._hist = hist
+        if isinstance(linewidth, float) or isinstance(linewidth, int):
+            from matplotlib import rc as rcp
+            rcp('lines', linewidth=linewidth)
+        if self._data: self.plot(self._data)
+
+    def set_linestyles(self, linestyles=None, linewidth=None):
+        """
+        Set the linestyles to be used. The plotter will cycle through
+        these linestyles when lines are overlaid (stacking mode) AND
+        only one color has been set.
+        Parameters:
+             linestyles:     a list of linestyles to use.
+                             'line', 'dashed', 'dotted', 'dashdot',
+                             'dashdotdot' and 'dashdashdot' are
+                             possible
+
+        Example:
+             plotter.set_colors("black")
+             plotter.set_linestyles("line dashed dotted dashdot")
+             # If for example four lines are overlaid e.g I Q U V
+             # 'I' will be 'solid', 'Q' will be 'dashed',
+             # U will be 'dotted' and 'V' will be 'dashdot'.
+        """
+        if isinstance(linestyles,str):
+            linestyles = linestyles.split()
+        self._plotter.palette(color=0,linestyle=0,linestyles=linestyles)
+        if isinstance(linewidth, float) or isinstance(linewidth, int):
+            from matplotlib import rc as rcp
+            rcp('lines', linewidth=linewidth)
+        if self._data: self.plot(self._data)
+
+    def set_font(self, family=None, style=None, weight=None, size=None):
+        """
+        Set font properties.
+        Parameters:
+            family:    one of 'sans-serif', 'serif', 'cursive', 'fantasy', 'monospace'
+            style:     one of 'normal' (or 'roman'), 'italic'  or 'oblique'
+            weight:    one of 'normal or 'bold'
+            size:      the 'general' font size, individual elements can be adjusted
+                       seperately
+        """
+        from matplotlib import rc as rcp
+        if isinstance(family, str):
+            rcp('font', family=family)
+        if isinstance(style, str):
+            rcp('font', style=style)
+        if isinstance(weight, str):
+            rcp('font', weight=weight)
+        if isinstance(size, float) or isinstance(size, int):
+            rcp('font', size=size)
+        if self._data: self.plot(self._data)
+
+    def plot_lines(self, linecat=None, doppler=0.0, deltachan=10, rotate=0.0,
+                   location=None):
+        """
+        Plot a line catalog.
+        Parameters:
+            linecat:      the linecatalog to plot
+            doppler:      the velocity shift to apply to the frequencies
+            deltachan:    the number of channels to include each side of the
+                          line to determine a local maximum/minimum
+            rotate:       the rotation for the text label
+            location:     the location of the line annotation from the 'top',
+                          'bottom' or alternate (None - the default)
+        Notes:
+        If the spectrum is flagged no line will be drawn in that location.
+        """
+        if not self._data: return
+        from asap._asap import linecatalog
+        if not isinstance(linecat, linecatalog): return
+        if not self._data.get_unit().endswith("GHz"): return
+        #self._plotter.hold()
+        from matplotlib.numerix import ma
+        for j in range(len(self._plotter.subplots)):
+            self._plotter.subplot(j)
+            lims = self._plotter.axes.get_xlim()
+            for row in range(linecat.nrow()):
+                restf = linecat.get_frequency(row)/1000.0
+                c = 299792.458
+                freq = restf*(1.0-doppler/c)
+                if lims[0] < freq < lims[1]:
+                    if location is None:
+                        loc = 'bottom'
+                        if row%2: loc='top'
+                    else: loc = location
+                    maxys = []
+                    for line in self._plotter.axes.lines:
+                        v = line._x
+                        asc = v[0] < v[-1]
+
+                        idx = None
+                        if not asc:
+                            if v[len(v)-1] <= freq <= v[0]:
+                                i = len(v)-1
+                                while i>=0 and v[i] < freq:
+                                    idx = i
+                                    i-=1
+                        else:
+                           if v[0] <= freq <= v[len(v)-1]:
+                                i = 0
+                                while  i<len(v) and v[i] < freq:
+                                    idx = i
+                                    i+=1
+                        if idx is not None:
+                            lower = idx - deltachan
+                            upper = idx + deltachan
+                            if lower < 0: lower = 0
+                            if upper > len(v): upper = len(v)
+                            s = slice(lower, upper)
+                            y = line._y[s]
+                            maxy = ma.maximum(y)
+                            if isinstance( maxy, float):
+                                maxys.append(maxy)
+                    if len(maxys):
+                        peak = max(maxys)
+                        if peak > self._plotter.axes.get_ylim()[1]:
+                            loc = 'bottom'
+                    else:
+                        continue
+                    self._plotter.vline_with_label(freq, peak,
+                                                   linecat.get_name(row),
+                                                   location=loc, rotate=rotate)
+        #        self._plotter.release()
+        self._plotter.show(hardrefresh=False)
+
+
+    def save(self, filename=None, orientation=None, dpi=None):
+        """
+        Save the plot to a file. The know formats are 'png', 'ps', 'eps'.
+        Parameters:
+             filename:    The name of the output file. This is optional
+                          and autodetects the image format from the file
+                          suffix. If non filename is specified a file
+                          called 'yyyymmdd_hhmmss.png' is created in the
+                          current directory.
+             orientation: optional parameter for postscript only (not eps).
+                          'landscape', 'portrait' or None (default) are valid.
+                          If None is choosen for 'ps' output, the plot is
+                          automatically oriented to fill the page.
+             dpi:         The dpi of the output non-ps plot
+        """
+        self._plotter.save(filename,orientation,dpi)
+        return
+
+
+    def set_mask(self, mask=None, selection=None):
+        """
+        Set a plotting mask for a specific polarization.
+        This is useful for masking out "noise" Pangle outside a source.
+        Parameters:
+             mask:           a mask from scantable.create_mask
+             selection:      the spectra to apply the mask to.
+        Example:
+             select = selector()
+             select.setpolstrings("Pangle")
+             plotter.set_mask(mymask, select)
+        """
+        if not self._data:
+            msg = "Can only set mask after a first call to plot()"
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise RuntimeError(msg)
+        if len(mask):
+            if isinstance(mask, list) or isinstance(mask, tuple):
+                self._usermask = array(mask)
+            else:
+                self._usermask = mask
+        if mask is None and selection is None:
+            self._usermask = []
+            self._maskselection = None
+        if isinstance(selection, selector):
+            self._maskselection = {'b': selection.get_beams(),
+                                   's': selection.get_scans(),
+                                   'i': selection.get_ifs(),
+                                   'p': selection.get_pols(),
+                                   't': [] }
+        else:
+            self._maskselection = None
+        self.plot(self._data)
+
+    def _slice_indeces(self, data):
+        mn = self._minmaxx[0]
+        mx = self._minmaxx[1]
+        asc = data[0] < data[-1]
+        start=0
+        end = len(data)-1
+        inc = 1
+        if not asc:
+            start = len(data)-1
+            end = 0
+            inc = -1
+        # find min index
+        while start > 0 and data[start] < mn:
+            start+= inc
+        # find max index
+        while end > 0 and data[end] > mx:
+            end-=inc
+        if end > 0: end +=1
+        if start > end:
+            return end,start
+        return start,end
+
+    def _reset(self):
+        self._usermask = []
+        self._usermaskspectra = None
+        self.set_selection(None, False)
+
+    def _plot(self, scan):
+        savesel = scan.get_selection()
+        sel = savesel +  self._selection
+        d0 = {'s': 'SCANNO', 'b': 'BEAMNO', 'i':'IFNO',
+              'p': 'POLNO', 'c': 'CYCLENO', 't' : 'TIME' }
+        order = [d0[self._panelling],d0[self._stacking]]
+        sel.set_order(order)
+        scan.set_selection(sel)
+        d = {'b': scan.getbeam, 's': scan.getscan,
+             'i': scan.getif, 'p': scan.getpol, 't': scan._gettime }
+
+        polmodes = dict(zip(self._selection.get_pols(),
+                            self._selection.get_poltypes()))
+        # this returns either a tuple of numbers or a length  (ncycles)
+        # convert this into lengths
+        n0,nstack0 = self._get_selected_n(scan)
+        if isinstance(n0, int): n = n0
+        else: n = len(n0)
+        if isinstance(nstack0, int): nstack = nstack0
+        else: nstack = len(nstack0)
+        maxpanel, maxstack = 16,8
+        if n > maxpanel or nstack > maxstack:
+            from asap import asaplog
+            maxn = 0
+            if nstack > maxstack: maxn = maxstack
+            if n > maxpanel: maxn = maxpanel
+            msg ="Scan to be plotted contains more than %d selections.\n" \
+                  "Selecting first %d selections..." % (maxn, maxn)
+            asaplog.push(msg)
+            print_log()
+            n = min(n,maxpanel)
+            nstack = min(nstack,maxstack)
+        if n > 1:
+            ganged = rcParams['plotter.ganged']
+            if self._rows and self._cols:
+                n = min(n,self._rows*self._cols)
+                self._plotter.set_panels(rows=self._rows,cols=self._cols,
+                                         nplots=n,ganged=ganged)
+            else:
+                self._plotter.set_panels(rows=n,cols=0,nplots=n,ganged=ganged)
+        else:
+            self._plotter.set_panels()
+        r=0
+        nr = scan.nrow()
+        a0,b0 = -1,-1
+        allxlim = []
+        allylim = []
+        newpanel=True
+        panelcount,stackcount = 0,0
+        while r < nr:
+            a = d[self._panelling](r)
+            b = d[self._stacking](r)
+            if a > a0 and panelcount < n:
+                if n > 1:
+                    self._plotter.subplot(panelcount)
+                self._plotter.palette(0)
+                #title
+                xlab = self._abcissa and self._abcissa[panelcount] \
+                       or scan._getabcissalabel()
+                ylab = self._ordinate and self._ordinate[panelcount] \
+                       or scan._get_ordinate_label()
+                self._plotter.set_axes('xlabel',xlab)
+                self._plotter.set_axes('ylabel',ylab)
+                lbl = self._get_label(scan, r, self._panelling, self._title)
+                if isinstance(lbl, list) or isinstance(lbl, tuple):
+                    if 0 <= panelcount < len(lbl):
+                        lbl = lbl[panelcount]
+                    else:
+                        # get default label
+                        lbl = self._get_label(scan, r, self._panelling, None)
+                self._plotter.set_axes('title',lbl)
+                newpanel = True
+                stackcount =0
+                panelcount += 1
+            if (b > b0 or newpanel) and stackcount < nstack:
+                y = []
+                if len(polmodes):
+                    y = scan._getspectrum(r, polmodes[scan.getpol(r)])
+                else:
+                    y = scan._getspectrum(r)
+                m = scan._getmask(r)
+                from matplotlib.numerix import logical_not, logical_and
+                if self._maskselection and len(self._usermask) == len(m):
+                    if d[self._stacking](r) in self._maskselection[self._stacking]:
+                        m = logical_and(m, self._usermask)
+                x = scan._getabcissa(r)
+                from matplotlib.numerix import ma, array
+                y = ma.masked_array(y,mask=logical_not(array(m,copy=False)))
+                if self._minmaxx is not None:
+                    s,e = self._slice_indeces(x)
+                    x = x[s:e]
+                    y = y[s:e]
+                if len(x) > 1024 and rcParams['plotter.decimate']:
+                    fac = len(x)/1024
+                    x = x[::fac]
+                    y = y[::fac]
+                llbl = self._get_label(scan, r, self._stacking, self._lmap)
+                if isinstance(llbl, list) or isinstance(llbl, tuple):
+                    if 0 <= stackcount < len(llbl):
+                        # use user label
+                        llbl = llbl[stackcount]
+                    else:
+                        # get default label
+                        llbl = self._get_label(scan, r, self._stacking, None)
+                self._plotter.set_line(label=llbl)
+                plotit = self._plotter.plot
+                if self._hist: plotit = self._plotter.hist
+                if len(x) > 0:
+                    plotit(x,y)
+                    xlim= self._minmaxx or [min(x),max(x)]
+                    allxlim += xlim
+                    ylim= self._minmaxy or [ma.minimum(y),ma.maximum(y)]
+                    allylim += ylim
+                stackcount += 1
+                # last in colour stack -> autoscale x
+                if stackcount == nstack:
+                    allxlim.sort()
+                    self._plotter.axes.set_xlim([allxlim[0],allxlim[-1]])
+                    # clear
+                    allxlim =[]
+
+            newpanel = False
+            a0=a
+            b0=b
+            # ignore following rows
+            if (panelcount == n) and (stackcount == nstack):
+                # last panel -> autoscale y if ganged
+                if rcParams['plotter.ganged']:
+                    allylim.sort()
+                    self._plotter.set_limits(ylim=[allylim[0],allylim[-1]])
+                break
+            r+=1 # next row
+        #reset the selector to the scantable's original
+        scan.set_selection(savesel)
+
+    def set_selection(self, selection=None, refresh=True):
+        self._selection = isinstance(selection,selector) and selection or selector()
+        d0 = {'s': 'SCANNO', 'b': 'BEAMNO', 'i':'IFNO',
+              'p': 'POLNO', 'c': 'CYCLENO', 't' : 'TIME' }
+        order = [d0[self._panelling],d0[self._stacking]]
+        self._selection.set_order(order)
+        if self._data and refresh: self.plot(self._data)
+
+    def _get_selected_n(self, scan):
+        d1 = {'b': scan.getbeamnos, 's': scan.getscannos,
+             'i': scan.getifnos, 'p': scan.getpolnos, 't': scan.ncycle }
+        d2 = { 'b': self._selection.get_beams(),
+               's': self._selection.get_scans(),
+               'i': self._selection.get_ifs(),
+               'p': self._selection.get_pols(),
+               't': self._selection.get_cycles() }
+        n =  d2[self._panelling] or d1[self._panelling]()
+        nstack = d2[self._stacking] or d1[self._stacking]()
+        return n,nstack
+
+    def _get_label(self, scan, row, mode, userlabel=None):
+        if isinstance(userlabel, list) and len(userlabel) == 0:
+            userlabel = " "
+        pms = dict(zip(self._selection.get_pols(),self._selection.get_poltypes()))
+        if len(pms):
+            poleval = scan._getpollabel(scan.getpol(row),pms[scan.getpol(row)])
+        else:
+            poleval = scan._getpollabel(scan.getpol(row),scan.poltype())
+        d = {'b': "Beam "+str(scan.getbeam(row)),
+             's': scan._getsourcename(row),
+             'i': "IF"+str(scan.getif(row)),
+             'p': poleval,
+             't': str(scan.get_time(row)) }
+        return userlabel or d[mode]
+
Index: tags/Release2.1.0b/python/asapreader.py
===================================================================
--- tags/Release2.1.0b/python/asapreader.py	(revision 1196)
+++ tags/Release2.1.0b/python/asapreader.py	(revision 1196)
@@ -0,0 +1,84 @@
+from asap._asap import stfiller
+from asap import print_log
+
+class reader(stfiller):
+    """
+    This class allows the user to import single dish files
+    (rpfits,sdfits,ms).
+    The reader reads in integrations from the file and remains at
+    the fileposition afterwards.
+    Available functions are:
+
+    read() # read until the (current) end of file)
+
+    Example:
+        r = reader('/tmp/P389.rpf')
+        scans r.read() # reads in the complete file into 'scans'
+        print scans    # summarises the contents
+        del r          # destroys the reader
+
+    IMPORTANT: Due to limitations in the rpfits library, only one reader
+               can be created at a time.
+               r = reader('XYZ.rpf')
+               r2 = reader('ABC.rpf')
+               is NOT possible. This is a limitation affecting
+               rpfits ONLY.
+    """
+
+    def __init__(self, filename, unit=None, theif=None, thebeam=None):
+        self.unit = unit
+        """
+        Parameters:
+            filename:    the name of an rpfits/sdfits/ms file on disk
+            unit:        brightness unit; must be consistent with K or Jy.
+                         The default is that a unit is set depending on
+                         the telescope.  Setting this over-rides that choice.
+            theif:       select a specific IF (default is all)
+            thebeam:     select a specific beam (default is all)
+        Example:
+            r = reader('/tmp/2001-09-01_0332_P363.rpf', theif=2)
+        """
+        if theif is None:
+            theif = -1
+        if thebeam is None:
+            thebeam = -1
+        from os.path import expandvars
+        filename = expandvars(filename)
+        stfiller.__init__(self, filename, theif, thebeam)
+        print_log()
+
+    def read(self):
+        """
+        Reads in all integrations in the data file.
+        """
+        from asap import scantable
+        from asap import asaplog
+        asaplog.push("Reading integrations from disk...")
+        stfiller._read(self)
+        tbl = stfiller._getdata(self)
+        if self.unit is not None:
+            tbl.set_fluxunit(self.unit)
+        print_log()
+        return scantable(tbl)
+
+    def close(self):
+        """
+        Close the reader.
+        """
+        self._close()
+
+    def summary(self, name=None):
+        """
+        Print a summary of all scans/integrations. This reads through the
+        whole file once.
+        Parameters:
+             None
+        Example:
+             r.summary()
+        """
+        stfiller._read(self)
+        from asap import scantable
+        tbl = scantable(stfiller._getdata(self))
+        tbl.summary(name)
+        del tbl
+        return
Index: tags/Release2.1.0b/python/linecatalog.py
===================================================================
--- tags/Release2.1.0b/python/linecatalog.py	(revision 1196)
+++ tags/Release2.1.0b/python/linecatalog.py	(revision 1196)
@@ -0,0 +1,120 @@
+"""
+A representation of a spectra line catalog.
+
+Author: Malte Marquarding
+
+"""
+__revision__ = "$Revision$"
+from asap._asap import linecatalog as lcbase
+from asap import rcParams
+import os
+
+class linecatalog(lcbase):
+    """
+    This class is a warpper for line catalogs. These can be either ASCII tables
+    or the tables saved from this class.
+    ASCII tables have the following restrictions:
+    Comments can be present through lines starting with '#'.
+    The first column contains the name of the Molecule. This can't contain spaces,
+    if it does it has to be wrapped in ""
+    The second column contains the frequency of the transition.
+    The third column contains the error in frequency.
+    The fourth column contains a value describing the intensity
+    """
+
+    def __init__(self, name):
+        fpath = os.path.abspath(os.path.expandvars(os.path.expanduser(name)))
+        lcbase.__init__(self, fpath)
+
+    def summary(self):
+        """
+        Print the contents of the table.
+        """
+        try:
+            from IPython.genutils import page as pager
+        except ImportError:
+            from pydoc import pager
+        pager(lcbase.summary(self, -1))
+
+    def set_name(self, name, mode="pattern"):
+        """
+        Set a name restriction on the table. This can be a standard unix-style
+        pattern or a regular expression.
+        Parameters:
+            name:       the name patterrn/regex
+            mode:       the matching mode, i.e. "pattern" (default) or "regex"
+        """
+        validmodes = "pattern regex".split()
+        if not mode.lower() in validmodes:
+            return
+        lcbase.set_name(self, name, mode)
+
+    def set_frequency_limits(self, fmin=1.0, fmax=120.0, unit="GHz"):
+        """
+        Set frequency limits on the table.
+        Parameters:
+            fmin:       the lower bound
+            fmax:       the upper bound
+            unit:       the frequency unit (default "GHz")
+
+        Note:
+            The underlying table conatins frequency values in MHz
+        """
+        base = { "GHz": 1000.0, "MHz": 1.0 }
+        if not base.has_key(unit):
+            raise ValueError("%s is not a valid unit." % unit)
+        # the table conatins values in MHz
+        lcbase.set_frequency_limits(self, fmin*base[unit], fmax*base[unit])
+
+    def set_strength_limits(self, smin, smax):
+        """
+        Set line strength limits on the table (arbitrary units)
+        Parameters:
+            smin:       the lower bound
+            smax:       the upper bound
+        """
+        lcbase.set_strength_limits(self, smin, smax)
+
+    def save(self, name, overwrite=False):
+        """
+        Save the subset of the table to disk. This uses an internal data format
+        and can be read in again.
+        """
+        name = os.path.expanduser(os.path.expandvars(name))
+        if os.path.isfile(name) or os.path.isdir(name):
+            if not overwrite:
+                msg = "File %s exists." % name
+                if rcParams['verbose']:
+                    print msg
+                    return
+                else:
+                    raise IOError(msg)
+        lcbase.save(self, name)
+
+    def reset(self):
+        """
+        Reset the table to its initial state, i.e. undo all calls to set_
+        """
+        lcbase.reset()
+
+    def get_row(self, row=0):
+        """
+        Get the values in a specified row of the table.
+        Parameters:
+              row:        the row to retrieve
+        """
+        freq = lcbase.get_frequency(self, row)
+        name = lcbase.get_name(self, row)
+        return { 'name':name, 'value': freq }
+
+    def __len__(self):
+        return self.nrow()
+
+    def __getitem__(self, k):
+        if k < 0: k = self.nrow()-k
+        return self.get_row(k)
+
+
+
+
+
Index: tags/Release2.1.0b/python/scantable.py
===================================================================
--- tags/Release2.1.0b/python/scantable.py	(revision 1196)
+++ tags/Release2.1.0b/python/scantable.py	(revision 1196)
@@ -0,0 +1,1671 @@
+from asap._asap import Scantable
+from asap import rcParams
+from asap import print_log
+from asap import asaplog
+from asap import selector
+from asap import NUM
+from asap import linecatalog
+
+class scantable(Scantable):
+    """
+        The ASAP container for scans
+    """
+
+    def __init__(self, filename, average=None, unit=None):
+        """
+        Create a scantable from a saved one or make a reference
+        Parameters:
+            filename:    the name of an asap table on disk
+                         or
+                         the name of a rpfits/sdfits/ms file
+                         (integrations within scans are auto averaged
+                         and the whole file is read)
+                         or
+                         [advanced] a reference to an existing
+                         scantable
+            average:     average all integrations withinb a scan on read.
+                         The default (True) is taken from .asaprc.
+            unit:         brightness unit; must be consistent with K or Jy.
+                         Over-rides the default selected by the reader
+                         (input rpfits/sdfits/ms) or replaces the value
+                         in existing scantables
+        """
+        if average is None:
+            average = rcParams['scantable.autoaverage']
+        #varlist = vars()
+        from asap._asap import stmath
+        self._math = stmath()
+        if isinstance(filename, Scantable):
+            Scantable.__init__(self, filename)
+        else:
+            if isinstance(filename, str):
+                import os.path
+                filename = os.path.expandvars(filename)
+                filename = os.path.expanduser(filename)
+                if not os.path.exists(filename):
+                    s = "File '%s' not found." % (filename)
+                    if rcParams['verbose']:
+                        asaplog.push(s)
+                        print asaplog.pop().strip()
+                        return
+                    raise IOError(s)
+                if os.path.isdir(filename) \
+                    and not os.path.exists(filename+'/table.f1'):
+                    # crude check if asap table
+                    if os.path.exists(filename+'/table.info'):
+                        ondisk = rcParams['scantable.storage'] == 'disk'
+                        Scantable.__init__(self, filename, ondisk)
+                        if unit is not None:
+                            self.set_fluxunit(unit)
+                        self.set_freqframe(rcParams['scantable.freqframe'])
+                    else:
+                        msg = "The given file '%s'is not a valid " \
+                              "asap table." % (filename)
+                        if rcParams['verbose']:
+                            print msg
+                            return
+                        else:
+                            raise IOError(msg)
+                else:
+                    self._fill([filename], unit, average)
+            elif (isinstance(filename, list) or isinstance(filename, tuple)) \
+                  and isinstance(filename[-1], str):
+                self._fill(filename, unit, average)
+        print_log()
+
+    def save(self, name=None, format=None, overwrite=False):
+        """
+        Store the scantable on disk. This can be an asap (aips++) Table, SDFITS,
+        Image FITS or MS2 format.
+        Parameters:
+            name:        the name of the outputfile. For format "ASCII"
+                         this is the root file name (data in 'name'.txt
+                         and header in 'name'_header.txt)
+            format:      an optional file format. Default is ASAP.
+                         Allowed are - 'ASAP' (save as ASAP [aips++] Table),
+                                       'SDFITS' (save as SDFITS file)
+                                       'ASCII' (saves as ascii text file)
+                                       'MS2' (saves as an aips++
+                                              MeasurementSet V2)
+            overwrite:   If the file should be overwritten if it exists.
+                         The default False is to return with warning
+                         without writing the output. USE WITH CARE.
+        Example:
+            scan.save('myscan.asap')
+            scan.save('myscan.sdfits', 'SDFITS')
+        """
+        from os import path
+        if format is None: format = rcParams['scantable.save']
+        suffix = '.'+format.lower()
+        if name is None or name == "":
+            name = 'scantable'+suffix
+            msg = "No filename given. Using default name %s..." % name
+            asaplog.push(msg)
+        name = path.expandvars(name)
+        if path.isfile(name) or path.isdir(name):
+            if not overwrite:
+                msg = "File %s exists." % name
+                if rcParams['verbose']:
+                    print msg
+                    return
+                else:
+                    raise IOError(msg)
+        format2 = format.upper()
+        if format2 == 'ASAP':
+            self._save(name)
+        else:
+            from asap._asap import stwriter as stw
+            writer = stw(format2)
+            writer.write(self, name)
+        print_log()
+        return
+
+    def copy(self):
+        """
+        Return a copy of this scantable.
+        Parameters:
+            none
+        Example:
+            copiedscan = scan.copy()
+        """
+        sd = scantable(Scantable._copy(self))
+        return sd
+
+    def drop_scan(self, scanid=None):
+        """
+        Return a new scantable where the specified scan number(s) has(have)
+        been dropped.
+        Parameters:
+            scanid:    a (list of) scan number(s)
+        """
+        from asap import _is_sequence_or_number as _is_valid
+        from asap import _to_list
+        from asap import unique
+        if not _is_valid(scanid):
+            if rcParams['verbose']:
+                print "Please specify a scanno to drop from the scantable"
+                return
+            else:
+                raise RuntimeError("No scan given")
+        try:
+            scanid = _to_list(scanid)
+            allscans = unique([ self.getscan(i) for i in range(self.nrow())])
+            for sid in scanid: allscans.remove(sid)
+            if len(allscans) == 0:
+                raise ValueError("Can't remove all scans")
+        except ValueError:
+            if rcParams['verbose']:
+                print "Couldn't find any match."
+                return
+            else: raise
+        try:
+            bsel = self.get_selection()
+            sel = selector()
+            sel.set_scans(allscans)
+            self.set_selection(bsel+sel)
+            scopy = self._copy()
+            self.set_selection(bsel)
+            return scantable(scopy)
+        except RuntimeError:
+            if rcParams['verbose']:
+                print "Couldn't find any match."
+            else:
+                raise
+
+
+    def get_scan(self, scanid=None):
+        """
+        Return a specific scan (by scanno) or collection of scans (by
+        source name) in a new scantable.
+        Parameters:
+            scanid:    a (list of) scanno or a source name, unix-style
+                       patterns are accepted for source name matching, e.g.
+                       '*_R' gets all 'ref scans
+        Example:
+            # get all scans containing the source '323p459'
+            newscan = scan.get_scan('323p459')
+            # get all 'off' scans
+            refscans = scan.get_scan('*_R')
+            # get a susbset of scans by scanno (as listed in scan.summary())
+            newscan = scan.get_scan([0, 2, 7, 10])
+        """
+        if scanid is None:
+            if rcParams['verbose']:
+                print "Please specify a scan no or name to " \
+                      "retrieve from the scantable"
+                return
+            else:
+                raise RuntimeError("No scan given")
+
+        try:
+            bsel = self.get_selection()
+            sel = selector()
+            if type(scanid) is str:
+                sel.set_name(scanid)
+                self.set_selection(bsel+sel)
+                scopy = self._copy()
+                self.set_selection(bsel)
+                return scantable(scopy)
+            elif type(scanid) is int:
+                sel.set_scans([scanid])
+                self.set_selection(bsel+sel)
+                scopy = self._copy()
+                self.set_selection(bsel)
+                return scantable(scopy)
+            elif type(scanid) is list:
+                sel.set_scans(scanid)
+                self.set_selection(sel)
+                scopy = self._copy()
+                self.set_selection(bsel)
+                return scantable(scopy)
+            else:
+                msg = "Illegal scanid type, use 'int' or 'list' if ints."
+                if rcParams['verbose']:
+                    print msg
+                else:
+                    raise TypeError(msg)
+        except RuntimeError:
+            if rcParams['verbose']: print "Couldn't find any match."
+            else: raise
+
+    def __str__(self):
+        return Scantable._summary(self, True)
+
+    def summary(self, filename=None):
+        """
+        Print a summary of the contents of this scantable.
+        Parameters:
+            filename:    the name of a file to write the putput to
+                         Default - no file output
+            verbose:     print extra info such as the frequency table
+                         The default (False) is taken from .asaprc
+        """
+        info = Scantable._summary(self, True)
+        #if verbose is None: verbose = rcParams['scantable.verbosesummary']
+        if filename is not None:
+            if filename is "":
+                filename = 'scantable_summary.txt'
+            from os.path import expandvars, isdir
+            filename = expandvars(filename)
+            if not isdir(filename):
+                data = open(filename, 'w')
+                data.write(info)
+                data.close()
+            else:
+                msg = "Illegal file name '%s'." % (filename)
+                if rcParams['verbose']:
+                    print msg
+                else:
+                    raise IOError(msg)
+        if rcParams['verbose']:
+            try:
+                from IPython.genutils import page as pager
+            except ImportError:
+                from pydoc import pager
+            pager(info)
+        else:
+            return info
+
+
+    def get_selection(self):
+        """
+        Get the selection object currently set on this scantable.
+        Parameters:
+            none
+        Example:
+            sel = scan.get_selection()
+            sel.set_ifs(0)              # select IF 0
+            scan.set_selection(sel)     # apply modified selection
+        """
+        return selector(self._getselection())
+
+    def set_selection(self, selection=selector()):
+        """
+        Select a subset of the data. All following operations on this scantable
+        are only applied to thi selection.
+        Parameters:
+            selection:    a selector object (default unset the selection)
+        Examples:
+            sel = selector()         # create a selection object
+            self.set_scans([0, 3])    # select SCANNO 0 and 3
+            scan.set_selection(sel)  # set the selection
+            scan.summary()           # will only print summary of scanno 0 an 3
+            scan.set_selection()     # unset the selection
+        """
+        self._setselection(selection)
+
+    def stats(self, stat='stddev', mask=None):
+        """
+        Determine the specified statistic of the current beam/if/pol
+        Takes a 'mask' as an optional parameter to specify which
+        channels should be excluded.
+        Parameters:
+            stat:    'min', 'max', 'sumsq', 'sum', 'mean'
+                     'var', 'stddev', 'avdev', 'rms', 'median'
+            mask:    an optional mask specifying where the statistic
+                     should be determined.
+        Example:
+            scan.set_unit('channel')
+            msk = scan.create_mask([100, 200], [500, 600])
+            scan.stats(stat='mean', mask=m)
+        """
+        if mask == None:
+            mask = []
+        axes = ['Beam', 'IF', 'Pol', 'Time']
+        if not self._check_ifs():
+            raise ValueError("Cannot apply mask as the IFs have different "
+                             "number of channels. Please use setselection() "
+                             "to select individual IFs")
+
+        statvals = self._math._stats(self, mask, stat)
+        out = ''
+        axes = []
+        for i in range(self.nrow()):
+            axis = []
+            axis.append(self.getscan(i))
+            axis.append(self.getbeam(i))
+            axis.append(self.getif(i))
+            axis.append(self.getpol(i))
+            axis.append(self.getcycle(i))
+            axes.append(axis)
+            tm = self._gettime(i)
+            src = self._getsourcename(i)
+            out += 'Scan[%d] (%s) ' % (axis[0], src)
+            out += 'Time[%s]:\n' % (tm)
+            if self.nbeam(-1) > 1: out +=  ' Beam[%d] ' % (axis[1])
+            if self.nif(-1) > 1: out +=  ' IF[%d] ' % (axis[2])
+            if self.npol(-1) > 1: out +=  ' Pol[%d] ' % (axis[3])
+            out += '= %3.3f\n' % (statvals[i])
+            out +=  "--------------------------------------------------\n"
+
+        if rcParams['verbose']:
+            print "--------------------------------------------------"
+            print " ", stat
+            print "--------------------------------------------------"
+            print out
+        retval = { 'axesnames': ['scanno', 'beamno', 'ifno', 'polno', 'cycleno'],
+                   'axes' : axes,
+                   'data': statvals}
+        return retval
+
+    def stddev(self, mask=None):
+        """
+        Determine the standard deviation of the current beam/if/pol
+        Takes a 'mask' as an optional parameter to specify which
+        channels should be excluded.
+        Parameters:
+            mask:    an optional mask specifying where the standard
+                     deviation should be determined.
+
+        Example:
+            scan.set_unit('channel')
+            msk = scan.create_mask([100, 200], [500, 600])
+            scan.stddev(mask=m)
+        """
+        return self.stats(stat='stddev', mask=mask);
+
+
+    def column_names(self):
+        """
+        Return a  list of column names, which can be used for selection.
+        """
+        return list(Scantable.column_names(self))
+
+    def get_tsys(self):
+        """
+        Return the System temperatures.
+        Parameters:
+
+        Returns:
+            a list of Tsys values for the current selection
+        """
+
+        return self._row_callback(self._gettsys, "Tsys")
+
+    def _row_callback(self, callback, label):
+        axes = []
+        axesnames = ['scanno', 'beamno', 'ifno', 'polno', 'cycleno']
+        out = ""
+        outvec = []
+        for i in range(self.nrow()):
+            axis = []
+            axis.append(self.getscan(i))
+            axis.append(self.getbeam(i))
+            axis.append(self.getif(i))
+            axis.append(self.getpol(i))
+            axis.append(self.getcycle(i))
+            axes.append(axis)
+            tm = self._gettime(i)
+            src = self._getsourcename(i)
+            out += 'Scan[%d] (%s) ' % (axis[0], src)
+            out += 'Time[%s]:\n' % (tm)
+            if self.nbeam(-1) > 1: out +=  ' Beam[%d] ' % (axis[1])
+            if self.nif(-1) > 1: out +=  ' IF[%d] ' % (axis[2])
+            if self.npol(-1) > 1: out +=  ' Pol[%d] ' % (axis[3])
+            outvec.append(callback(i))
+            out += '= %3.3f\n' % (outvec[i])
+            out +=  "--------------------------------------------------\n"
+        if rcParams['verbose']:
+            print "--------------------------------------------------"
+            print " %s" % (label)
+            print "--------------------------------------------------"
+            print out
+        # disabled because the vector seems more useful
+        #retval = {'axesnames': axesnames, 'axes': axes, 'data': outvec}
+        return outvec
+
+    def _get_column(self, callback, row=-1):
+        """
+        """
+        if row == -1:
+            return [callback(i) for i in range(self.nrow())]
+        else:
+            if  0 <= row < self.nrow():
+                return callback(row)
+
+
+    def get_time(self, row=-1):
+        """
+        Get a list of time stamps for the observations.
+        Return a string for each integration in the scantable.
+        Parameters:
+            row:    row no of integration. Default -1 return all rows
+        Example:
+            none
+        """
+        from time import strptime
+        from datetime import datetime
+        times = self._get_column(self._gettime, row)
+        format = "%Y/%m/%d/%H:%M:%S"
+        if isinstance(times, list):
+            return [datetime(*strptime(i, format)[:6]) for i in times]
+        else:
+            return datetime(*strptime(times, format)[:6])
+
+    def get_sourcename(self, row=-1):
+        """
+        Get a list source names for the observations.
+        Return a string for each integration in the scantable.
+        Parameters:
+            row:    row no of integration. Default -1 return all rows
+        Example:
+            none
+        """
+        return self._get_column(self._getsourcename, row)
+
+    def get_elevation(self, row=-1):
+        """
+        Get a list of elevations for the observations.
+        Return a float for each integration in the scantable.
+        Parameters:
+            row:    row no of integration. Default -1 return all rows
+        Example:
+            none
+        """
+        return self._get_column(self._getelevation, row)
+
+    def get_azimuth(self, row=-1):
+        """
+        Get a list of azimuths for the observations.
+        Return a float for each integration in the scantable.
+        Parameters:
+            row:    row no of integration. Default -1 return all rows
+        Example:
+            none
+        """
+        return self._get_column(self._getazimuth, row)
+
+    def get_parangle(self, row=-1):
+        """
+        Get a list of parallactic angles for the observations.
+        Return a float for each integration in the scantable.
+        Parameters:
+            row:    row no of integration. Default -1 return all rows
+        Example:
+            none
+        """
+        return self._get_column(self._getparangle, row)
+
+    def get_direction(self, row=-1):
+        """
+        Get a list of Positions on the sky (direction) for the observations.
+        Return a float for each integration in the scantable.
+        Parameters:
+            row:    row no of integration. Default -1 return all rows
+        Example:
+            none
+        """
+        return self._get_column(self._getdirection, row)
+
+    def set_unit(self, unit='channel'):
+        """
+        Set the unit for all following operations on this scantable
+        Parameters:
+            unit:    optional unit, default is 'channel'
+                     one of '*Hz', 'km/s', 'channel', ''
+        """
+        varlist = vars()
+        if unit in ['', 'pixel', 'channel']:
+            unit = ''
+        inf = list(self._getcoordinfo())
+        inf[0] = unit
+        self._setcoordinfo(inf)
+        self._add_history("set_unit", varlist)
+
+    def set_instrument(self, instr):
+        """
+        Set the instrument for subsequent processing
+        Parameters:
+            instr:    Select from 'ATPKSMB', 'ATPKSHOH', 'ATMOPRA',
+                      'DSS-43' (Tid), 'CEDUNA', and 'HOBART'
+        """
+        self._setInstrument(instr)
+        self._add_history("set_instument", vars())
+        print_log()
+
+    def set_feedtype(self, feedtype):
+        """
+        Overwrite the feed type, which might not be set correctly.
+        Parameters:
+            feedtype:     'linear' or 'circular'
+        """
+        self._setfeedtype(feedtype)
+        self._add_history("set_feedtype", vars())
+        print_log()
+
+    def set_doppler(self, doppler='RADIO'):
+        """
+        Set the doppler for all following operations on this scantable.
+        Parameters:
+            doppler:    One of 'RADIO', 'OPTICAL', 'Z', 'BETA', 'GAMMA'
+        """
+        varlist = vars()
+        inf = list(self._getcoordinfo())
+        inf[2] = doppler
+        self._setcoordinfo(inf)
+        self._add_history("set_doppler", vars())
+        print_log()
+
+    def set_freqframe(self, frame=None):
+        """
+        Set the frame type of the Spectral Axis.
+        Parameters:
+            frame:   an optional frame type, default 'LSRK'. Valid frames are:
+                     'REST', 'TOPO', 'LSRD', 'LSRK', 'BARY',
+                     'GEO', 'GALACTO', 'LGROUP', 'CMB'
+        Examples:
+            scan.set_freqframe('BARY')
+        """
+        if frame is None: frame = rcParams['scantable.freqframe']
+        varlist = vars()
+        valid = ['REST', 'TOPO', 'LSRD', 'LSRK', 'BARY', \
+                   'GEO', 'GALACTO', 'LGROUP', 'CMB']
+
+        if frame in valid:
+            inf = list(self._getcoordinfo())
+            inf[1] = frame
+            self._setcoordinfo(inf)
+            self._add_history("set_freqframe", varlist)
+        else:
+            msg  = "Please specify a valid freq type. Valid types are:\n", valid
+            if rcParams['verbose']:
+                print msg
+            else:
+                raise TypeError(msg)
+        print_log()
+
+    def set_dirframe(self, frame=""):
+        """
+        Set the frame type of the Direction on the sky.
+        Parameters:
+            frame:   an optional frame type, default ''. Valid frames are:
+                     'J2000', 'B1950', 'GALACTIC'
+        Examples:
+            scan.set_dirframe('GALACTIC')
+        """
+        varlist = vars()
+        try:
+            Scantable.set_dirframe(self, frame)
+        except RuntimeError, msg:
+            if rcParams['verbose']:
+                print msg
+            else:
+                raise
+        self._add_history("set_dirframe", varlist)
+
+    def get_unit(self):
+        """
+        Get the default unit set in this scantable
+        Parameters:
+        Returns:
+            A unit string
+        """
+        inf = self._getcoordinfo()
+        unit = inf[0]
+        if unit == '': unit = 'channel'
+        return unit
+
+    def get_abcissa(self, rowno=0):
+        """
+        Get the abcissa in the current coordinate setup for the currently
+        selected Beam/IF/Pol
+        Parameters:
+            rowno:    an optional row number in the scantable. Default is the
+                      first row, i.e. rowno=0
+        Returns:
+            The abcissa values and it's format string (as a dictionary)
+        """
+        abc = self._getabcissa(rowno)
+        lbl = self._getabcissalabel(rowno)
+        print_log()
+        return abc, lbl
+
+    def flag(self, mask=None):
+        """
+        Flag the selected data using an optional channel mask.
+        Parameters:
+            mask:   an optional channel mask, created with create_mask. Default
+                    (no mask) is all channels.
+        """
+        varlist = vars()
+        if mask is None:
+            mask = []
+        try:
+            self._flag(mask)
+        except RuntimeError, msg:
+            if rcParams['verbose']:
+                print msg
+                return
+            else: raise
+        self._add_history("flag", varlist)
+
+    def lag_flag(self, frequency, unit="GHz", width=0, insitu=None):
+        """
+        Flag the data in 'lag' space by providing a frequency to remove.
+        Flagged data in the scantable gets set to 0.0 before the fft.
+        No taper is applied.
+        Parameters:
+            frequency:    the frequency to remove
+            unit:         the frequency unit ()default "GHz")
+            width:        the number of 'lag' channels to extent the frequency
+                          by, i.e. the interval
+                          [bandwidth/frequency-width, bandwidth/frequency+width]
+        """
+        if insitu is None: insitu = rcParams['insitu']
+        self._math._setinsitu(insitu)
+        varlist = vars()
+        base = { "GHz": 100000000., "MHz": 1000000., "kHz": 1000., "Hz": 1. }
+        if not base.has_key(unit):
+            raise ValueError("%s is not a valid unit." % unit)
+        try:
+            s = scantable(self._math._lag_flag(self, frequency*base[unit], width))
+        except RuntimeError, msg:
+            if rcParams['verbose']:
+                print msg
+                return
+            else: raise
+        s._add_history("lag_flag", varlist)
+        print_log()
+        if insitu:
+            self._assign(s)
+        else:
+            return s
+
+
+    def create_mask(self, *args, **kwargs):
+        """
+        Compute and return a mask based on [min, max] windows.
+        The specified windows are to be INCLUDED, when the mask is
+        applied.
+        Parameters:
+            [min, max], [min2, max2], ...
+                Pairs of start/end points (inclusive)specifying the regions
+                to be masked
+            invert:     optional argument. If specified as True,
+                        return an inverted mask, i.e. the regions
+                        specified are EXCLUDED
+            row:        create the mask using the specified row for
+                        unit conversions, default is row=0
+                        only necessary if frequency varies over rows.
+        Example:
+            scan.set_unit('channel')
+            a)
+            msk = scan.create_mask([400, 500], [800, 900])
+            # masks everything outside 400 and 500
+            # and 800 and 900 in the unit 'channel'
+
+            b)
+            msk = scan.create_mask([400, 500], [800, 900], invert=True)
+            # masks the regions between 400 and 500
+            # and 800 and 900 in the unit 'channel'
+            c)
+            mask only channel 400
+            msk =  scan.create_mask([400, 400])
+        """
+        row = 0
+        if kwargs.has_key("row"):
+            row = kwargs.get("row")
+        data = self._getabcissa(row)
+        u = self._getcoordinfo()[0]
+        if rcParams['verbose']:
+            if u == "": u = "channel"
+            msg = "The current mask window unit is %s" % u
+            i = self._check_ifs()
+            if not i:
+                msg += "\nThis mask is only valid for IF=%d" % (self.getif(i))
+            asaplog.push(msg)
+        n = self.nchan()
+        msk = NUM.zeros(n)
+        # test if args is a 'list' or a 'normal *args - UGLY!!!
+
+        ws = (isinstance(args[-1][-1], int) or isinstance(args[-1][-1], float)) \
+             and args or args[0]
+        for window in ws:
+            if (len(window) != 2 or window[0] > window[1] ):
+                raise TypeError("A window needs to be defined as [min, max]")
+            for i in range(n):
+                if data[i] >= window[0] and data[i] <= window[1]:
+                    msk[i] = 1
+        if kwargs.has_key('invert'):
+            if kwargs.get('invert'):
+                msk = NUM.logical_not(msk)
+        print_log()
+        return msk
+
+    def get_restfreqs(self):
+        """
+        Get the restfrequency(s) stored in this scantable.
+        The return value(s) are always of unit 'Hz'
+        Parameters:
+            none
+        Returns:
+            a list of doubles
+        """
+        return list(self._getrestfreqs())
+
+
+    def set_restfreqs(self, freqs=None, unit='Hz'):
+        """
+        Set or replace the restfrequency specified and
+        If the 'freqs' argument holds a scalar,
+        then that rest frequency will be applied to all the selected
+        data.  If the 'freqs' argument holds
+        a vector, then it MUST be of equal or smaller length than
+        the number of IFs (and the available restfrequencies will be
+        replaced by this vector).  In this case, *all* data have
+        the restfrequency set per IF according
+        to the corresponding value you give in the 'freqs' vector.
+        E.g. 'freqs=[1e9, 2e9]'  would mean IF 0 gets restfreq 1e9 and
+        IF 1 gets restfreq 2e9.
+        You can also specify the frequencies via a linecatalog/
+
+        Parameters:
+            freqs:   list of rest frequency values or string idenitfiers
+            unit:    unit for rest frequency (default 'Hz')
+
+        Example:
+            # set the given restfrequency for the whole table
+            scan.set_restfreqs(freqs=1.4e9)
+            # If thee number of IFs in the data is >= 2 the IF0 gets the first
+            # value IF1 the second...
+            scan.set_restfreqs(freqs=[1.4e9, 1.67e9])
+            #set the given restfrequency for the whole table (by name)
+            scan.set_restfreqs(freqs="OH1667")
+
+        Note:
+            To do more sophisticate Restfrequency setting, e.g. on a
+            source and IF basis, use scantable.set_selection() before using
+            this function.
+            # provide your scantable is call scan
+            selection = selector()
+            selection.set_name("ORION*")
+            selection.set_ifs([1])
+            scan.set_selection(selection)
+            scan.set_restfreqs(freqs=86.6e9)
+
+        """
+        varlist = vars()
+        from asap import linecatalog
+        # simple  value
+        if isinstance(freqs, int) or isinstance(freqs, float):
+            self._setrestfreqs(freqs, "",unit)
+        # list of values
+        elif isinstance(freqs, list) or isinstance(freqs, tuple):
+            # list values are scalars
+            if isinstance(freqs[-1], int) or isinstance(freqs[-1], float):
+                sel = selector()
+                savesel = self._getselection()
+                for i in xrange(len(freqs)):
+                    sel.set_ifs([i])
+                    self._setselection(sel)
+                    self._setrestfreqs(freqs[i], "",unit)
+                self._setselection(savesel)
+            # list values are tuples, (value, name)
+            elif isinstance(freqs[-1], dict):
+                sel = selector()
+                savesel = self._getselection()
+                for i in xrange(len(freqs)):
+                    sel.set_ifs([i])
+                    self._setrestfreqs(freqs[i]["value"],
+                                       freqs[i]["name"], "MHz")
+                    self._setselection(sel)
+                self._setselection(savesel)
+        # freqs are to be taken from a linecatalog
+        elif isinstance(freqs, linecatalog):
+            sel = selector()
+            savesel = self._getselection()
+            for i in xrange(freqs.nrow()):
+                sel.set_ifs([i])
+                self._setselection(sel)
+                self._setrestfreqs(freqs.get_frequency(i),
+                                   freqs.get_name(i), "MHz")
+                # ensure that we are not iterating past nIF
+                if i == self.nif()-1: break
+            self._setselection(savesel)
+        else:
+            return
+        self._add_history("set_restfreqs", varlist)
+
+
+
+    def history(self):
+        hist = list(self._gethistory())
+        out = "-"*80
+        for h in hist:
+            if h.startswith("---"):
+                out += "\n"+h
+            else:
+                items = h.split("##")
+                date = items[0]
+                func = items[1]
+                items = items[2:]
+                out += "\n"+date+"\n"
+                out += "Function: %s\n  Parameters:" % (func)
+                for i in items:
+                    s = i.split("=")
+                    out += "\n   %s = %s" % (s[0], s[1])
+                out += "\n"+"-"*80
+        try:
+            from IPython.genutils import page as pager
+        except ImportError:
+            from pydoc import pager
+        pager(out)
+        return
+
+    #
+    # Maths business
+    #
+
+    def average_time(self, mask=None, scanav=False, weight='tint', align=False):
+        """
+        Return the (time) weighted average of a scan.
+        Note:
+            in channels only - align if necessary
+        Parameters:
+            mask:     an optional mask (only used for 'var' and 'tsys'
+                      weighting)
+            scanav:   True averages each scan separately
+                      False (default) averages all scans together,
+            weight:   Weighting scheme.
+                      'none'     (mean no weight)
+                      'var'      (1/var(spec) weighted)
+                      'tsys'     (1/Tsys**2 weighted)
+                      'tint'     (integration time weighted)
+                      'tintsys'  (Tint/Tsys**2)
+                      'median'   ( median averaging)
+                      The default is 'tint'
+            align:    align the spectra in velocity before averaging. It takes
+                      the time of the first spectrum as reference time.
+        Example:
+            # time average the scantable without using a mask
+            newscan = scan.average_time()
+        """
+        varlist = vars()
+        if weight is None: weight = 'TINT'
+        if mask is None: mask = ()
+        if scanav: scanav = "SCAN"
+        else: scanav = "NONE"
+        scan = (self, )
+        try:
+            if align:
+                scan = (self.freq_align(insitu=False), )
+            s = None
+            if weight.upper() == 'MEDIAN':
+                s = scantable(self._math._averagechannel(scan[0], 'MEDIAN',
+                                                         scanav))
+            else:
+                s = scantable(self._math._average(scan, mask, weight.upper(),
+                              scanav))
+        except RuntimeError, msg:
+            if rcParams['verbose']:
+                print msg
+                return
+            else: raise
+        s._add_history("average_time", varlist)
+        print_log()
+        return s
+
+    def convert_flux(self, jyperk=None, eta=None, d=None, insitu=None):
+        """
+        Return a scan where all spectra are converted to either
+        Jansky or Kelvin depending upon the flux units of the scan table.
+        By default the function tries to look the values up internally.
+        If it can't find them (or if you want to over-ride), you must
+        specify EITHER jyperk OR eta (and D which it will try to look up
+        also if you don't set it). jyperk takes precedence if you set both.
+        Parameters:
+            jyperk:      the Jy / K conversion factor
+            eta:         the aperture efficiency
+            d:           the geomtric diameter (metres)
+            insitu:      if False a new scantable is returned.
+                         Otherwise, the scaling is done in-situ
+                         The default is taken from .asaprc (False)
+            allaxes:         if True apply to all spectra. Otherwise
+                         apply only to the selected (beam/pol/if)spectra only
+                         The default is taken from .asaprc (True if none)
+        """
+        if insitu is None: insitu = rcParams['insitu']
+        self._math._setinsitu(insitu)
+        varlist = vars()
+        if jyperk is None: jyperk = -1.0
+        if d is None: d = -1.0
+        if eta is None: eta = -1.0
+        s = scantable(self._math._convertflux(self, d, eta, jyperk))
+        s._add_history("convert_flux", varlist)
+        print_log()
+        if insitu: self._assign(s)
+        else: return s
+
+    def gain_el(self, poly=None, filename="", method="linear", insitu=None):
+        """
+        Return a scan after applying a gain-elevation correction.
+        The correction can be made via either a polynomial or a
+        table-based interpolation (and extrapolation if necessary).
+        You specify polynomial coefficients, an ascii table or neither.
+        If you specify neither, then a polynomial correction will be made
+        with built in coefficients known for certain telescopes (an error
+        will occur if the instrument is not known).
+        The data and Tsys are *divided* by the scaling factors.
+        Parameters:
+            poly:        Polynomial coefficients (default None) to compute a
+                         gain-elevation correction as a function of
+                         elevation (in degrees).
+            filename:    The name of an ascii file holding correction factors.
+                         The first row of the ascii file must give the column
+                         names and these MUST include columns
+                         "ELEVATION" (degrees) and "FACTOR" (multiply data
+                         by this) somewhere.
+                         The second row must give the data type of the
+                         column. Use 'R' for Real and 'I' for Integer.
+                         An example file would be
+                         (actual factors are arbitrary) :
+
+                         TIME ELEVATION FACTOR
+                         R R R
+                         0.1 0 0.8
+                         0.2 20 0.85
+                         0.3 40 0.9
+                         0.4 60 0.85
+                         0.5 80 0.8
+                         0.6 90 0.75
+            method:      Interpolation method when correcting from a table.
+                         Values are  "nearest", "linear" (default), "cubic"
+                         and "spline"
+            insitu:      if False a new scantable is returned.
+                         Otherwise, the scaling is done in-situ
+                         The default is taken from .asaprc (False)
+        """
+
+        if insitu is None: insitu = rcParams['insitu']
+        self._math._setinsitu(insitu)
+        varlist = vars()
+        if poly is None:
+            poly = ()
+        from os.path import expandvars
+        filename = expandvars(filename)
+        s = scantable(self._math._gainel(self, poly, filename, method))
+        s._add_history("gain_el", varlist)
+        print_log()
+        if insitu: self._assign(s)
+        else: return s
+
+    def freq_align(self, reftime=None, method='cubic', insitu=None):
+        """
+        Return a scan where all rows have been aligned in frequency/velocity.
+        The alignment frequency frame (e.g. LSRK) is that set by function
+        set_freqframe.
+        Parameters:
+            reftime:     reference time to align at. By default, the time of
+                         the first row of data is used.
+            method:      Interpolation method for regridding the spectra.
+                         Choose from "nearest", "linear", "cubic" (default)
+                         and "spline"
+            insitu:      if False a new scantable is returned.
+                         Otherwise, the scaling is done in-situ
+                         The default is taken from .asaprc (False)
+        """
+        if insitu is None: insitu = rcParams["insitu"]
+        self._math._setinsitu(insitu)
+        varlist = vars()
+        if reftime is None: reftime = ""
+        s = scantable(self._math._freq_align(self, reftime, method))
+        s._add_history("freq_align", varlist)
+        print_log()
+        if insitu: self._assign(s)
+        else: return s
+
+    def opacity(self, tau, insitu=None):
+        """
+        Apply an opacity correction. The data
+        and Tsys are multiplied by the correction factor.
+        Parameters:
+            tau:         Opacity from which the correction factor is
+                         exp(tau*ZD)
+                         where ZD is the zenith-distance
+            insitu:      if False a new scantable is returned.
+                         Otherwise, the scaling is done in-situ
+                         The default is taken from .asaprc (False)
+        """
+        if insitu is None: insitu = rcParams['insitu']
+        self._math._setinsitu(insitu)
+        varlist = vars()
+        s = scantable(self._math._opacity(self, tau))
+        s._add_history("opacity", varlist)
+        print_log()
+        if insitu: self._assign(s)
+        else: return s
+
+    def bin(self, width=5, insitu=None):
+        """
+        Return a scan where all spectra have been binned up.
+            width:       The bin width (default=5) in pixels
+            insitu:      if False a new scantable is returned.
+                         Otherwise, the scaling is done in-situ
+                         The default is taken from .asaprc (False)
+        """
+        if insitu is None: insitu = rcParams['insitu']
+        self._math._setinsitu(insitu)
+        varlist = vars()
+        s = scantable(self._math._bin(self, width))
+        s._add_history("bin", varlist)
+        print_log()
+        if insitu: self._assign(s)
+        else: return s
+
+
+    def resample(self, width=5, method='cubic', insitu=None):
+        """
+        Return a scan where all spectra have been binned up
+            width:       The bin width (default=5) in pixels
+            method:      Interpolation method when correcting from a table.
+                         Values are  "nearest", "linear", "cubic" (default)
+                         and "spline"
+            insitu:      if False a new scantable is returned.
+                         Otherwise, the scaling is done in-situ
+                         The default is taken from .asaprc (False)
+        """
+        if insitu is None: insitu = rcParams['insitu']
+        self._math._setinsitu(insitu)
+        varlist = vars()
+        s = scantable(self._math._resample(self, method, width))
+        s._add_history("resample", varlist)
+        print_log()
+        if insitu: self._assign(s)
+        else: return s
+
+
+    def average_pol(self, mask=None, weight='none'):
+        """
+        Average the Polarisations together.
+        Parameters:
+            mask:        An optional mask defining the region, where the
+                         averaging will be applied. The output will have all
+                         specified points masked.
+            weight:      Weighting scheme. 'none' (default), 'var' (1/var(spec)
+                         weighted), or 'tsys' (1/Tsys**2 weighted)
+        """
+        varlist = vars()
+        if mask is None:
+            mask = ()
+        s = scantable(self._math._averagepol(self, mask, weight.upper()))
+        s._add_history("average_pol", varlist)
+        print_log()
+        return s
+
+    def average_beam(self, mask=None, weight='none'):
+        """
+        Average the Beams together.
+        Parameters:
+            mask:        An optional mask defining the region, where the
+                         averaging will be applied. The output will have all
+                         specified points masked.
+            weight:      Weighting scheme. 'none' (default), 'var' (1/var(spec)
+                         weighted), or 'tsys' (1/Tsys**2 weighted)
+        """
+        varlist = vars()
+        if mask is None:
+            mask = ()
+        s = scantable(self._math._averagebeams(self, mask, weight.upper()))
+        s._add_history("average_beam", varlist)
+        print_log()
+        return s
+
+    def convert_pol(self, poltype=None):
+        """
+        Convert the data to a different polarisation type.
+        Parameters:
+            poltype:    The new polarisation type. Valid types are:
+                        "linear", "stokes" and "circular"
+        """
+        varlist = vars()
+        try:
+            s = scantable(self._math._convertpol(self, poltype))
+        except RuntimeError, msg:
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise
+        s._add_history("convert_pol", varlist)
+        print_log()
+        return s
+
+    def smooth(self, kernel="hanning", width=5.0, insitu=None):
+        """
+        Smooth the spectrum by the specified kernel (conserving flux).
+        Parameters:
+            scan:       The input scan
+            kernel:     The type of smoothing kernel. Select from
+                        'hanning' (default), 'gaussian' and 'boxcar'.
+                        The first three characters are sufficient.
+            width:      The width of the kernel in pixels. For hanning this is
+                        ignored otherwise it defauls to 5 pixels.
+                        For 'gaussian' it is the Full Width Half
+                        Maximum. For 'boxcar' it is the full width.
+            insitu:     if False a new scantable is returned.
+                        Otherwise, the scaling is done in-situ
+                        The default is taken from .asaprc (False)
+        Example:
+             none
+        """
+        if insitu is None: insitu = rcParams['insitu']
+        self._math._setinsitu(insitu)
+        varlist = vars()
+        s = scantable(self._math._smooth(self, kernel.lower(), width))
+        s._add_history("smooth", varlist)
+        print_log()
+        if insitu: self._assign(s)
+        else: return s
+
+
+    def poly_baseline(self, mask=None, order=0, plot=False, insitu=None):
+        """
+        Return a scan which has been baselined (all rows) by a polynomial.
+        Parameters:
+            scan:       a scantable
+            mask:       an optional mask
+            order:      the order of the polynomial (default is 0)
+            plot:       plot the fit and the residual. In this each
+                        indivual fit has to be approved, by typing 'y'
+                        or 'n'
+            insitu:     if False a new scantable is returned.
+                        Otherwise, the scaling is done in-situ
+                        The default is taken from .asaprc (False)
+        Example:
+            # return a scan baselined by a third order polynomial,
+            # not using a mask
+            bscan = scan.poly_baseline(order=3)
+        """
+        if insitu is None: insitu = rcParams['insitu']
+        varlist = vars()
+        if mask is None:
+            mask = list(NUM.ones(self.nchan(-1)))
+        from asap.asapfitter import fitter
+        f = fitter()
+        f.set_scan(self, mask)
+        f.set_function(poly=order)
+        s = f.auto_fit(insitu, plot=plot)
+        s._add_history("poly_baseline", varlist)
+        print_log()
+        if insitu: self._assign(s)
+        else: return s
+
+    def auto_poly_baseline(self, mask=[], edge=(0, 0), order=0,
+                           threshold=3, plot=False, insitu=None):
+        """
+        Return a scan which has been baselined (all rows) by a polynomial.
+        Spectral lines are detected first using linefinder and masked out
+        to avoid them affecting the baseline solution.
+
+        Parameters:
+            mask:       an optional mask retreived from scantable
+            edge:       an optional number of channel to drop at
+                        the edge of spectrum. If only one value is
+                        specified, the same number will be dropped from
+                        both sides of the spectrum. Default is to keep
+                        all channels. Nested tuples represent individual
+                        edge selection for different IFs (a number of spectral
+                        channels can be different)
+            order:      the order of the polynomial (default is 0)
+            threshold:  the threshold used by line finder. It is better to
+                        keep it large as only strong lines affect the
+                        baseline solution.
+            plot:       plot the fit and the residual. In this each
+                        indivual fit has to be approved, by typing 'y'
+                        or 'n'
+            insitu:     if False a new scantable is returned.
+                        Otherwise, the scaling is done in-situ
+                        The default is taken from .asaprc (False)
+
+        Example:
+            scan2=scan.auto_poly_baseline(order=7)
+        """
+        if insitu is None: insitu = rcParams['insitu']
+        varlist = vars()
+        from asap.asapfitter import fitter
+        from asap.asaplinefind import linefinder
+        from asap import _is_sequence_or_number as _is_valid
+
+        # check whether edge is set up for each IF individually
+        individualedge = False;
+        if len(edge) > 1:
+            if isinstance(edge[0], list) or isinstance(edge[0], tuple):
+                individualedge = True;
+
+        if not _is_valid(edge, int) and not individualedge:
+            raise ValueError, "Parameter 'edge' has to be an integer or a \
+            pair of integers specified as a tuple. Nested tuples are allowed \
+            to make individual selection for different IFs."
+
+        curedge = (0, 0)
+        if individualedge:
+            for edgepar in edge:
+                if not _is_valid(edgepar, int):
+                    raise ValueError, "Each element of the 'edge' tuple has \
+                                       to be a pair of integers or an integer."
+        else:
+            curedge = edge;
+
+        # setup fitter
+        f = fitter()
+        f.set_function(poly=order)
+
+        # setup line finder
+        fl = linefinder()
+        fl.set_options(threshold=threshold)
+
+        if not insitu:
+            workscan = self.copy()
+        else:
+            workscan = self
+
+        fl.set_scan(workscan)
+
+        rows = range(workscan.nrow())
+        asaplog.push("Processing:")
+        for r in rows:
+            msg = " Scan[%d] Beam[%d] IF[%d] Pol[%d] Cycle[%d]" % \
+                (workscan.getscan(r), workscan.getbeam(r), workscan.getif(r), \
+                 workscan.getpol(r), workscan.getcycle(r))
+            asaplog.push(msg, False)
+
+            # figure out edge parameter
+            if individualedge:
+                if len(edge) >= workscan.getif(r):
+                    raise RuntimeError, "Number of edge elements appear to " \
+                                        "be less than the number of IFs"
+                    curedge = edge[workscan.getif(r)]
+
+            # setup line finder
+            fl.find_lines(r, mask, curedge)
+            f.set_scan(workscan, fl.get_mask())
+            f.x = workscan._getabcissa(r)
+            f.y = workscan._getspectrum(r)
+            f.data = None
+            f.fit()
+            x = f.get_parameters()
+            if plot:
+                f.plot(residual=True)
+                x = raw_input("Accept fit ( [y]/n ): ")
+                if x.upper() == 'N':
+                    continue
+            workscan._setspectrum(f.fitter.getresidual(), r)
+        if plot:
+            f._p.unmap()
+            f._p = None
+        workscan._add_history("auto_poly_baseline", varlist)
+        if insitu:
+            self._assign(workscan)
+        else:
+            return workscan
+
+    def rotate_linpolphase(self, angle):
+        """
+        Rotate the phase of the complex polarization O=Q+iU correlation.
+        This is always done in situ in the raw data.  So if you call this
+        function more than once then each call rotates the phase further.
+        Parameters:
+            angle:   The angle (degrees) to rotate (add) by.
+        Examples:
+            scan.rotate_linpolphase(2.3)
+        """
+        varlist = vars()
+        self._math._rotate_linpolphase(self, angle)
+        self._add_history("rotate_linpolphase", varlist)
+        print_log()
+        return
+
+
+    def rotate_xyphase(self, angle):
+        """
+        Rotate the phase of the XY correlation.  This is always done in situ
+        in the data.  So if you call this function more than once
+        then each call rotates the phase further.
+        Parameters:
+            angle:   The angle (degrees) to rotate (add) by.
+        Examples:
+            scan.rotate_xyphase(2.3)
+        """
+        varlist = vars()
+        self._math._rotate_xyphase(self, angle)
+        self._add_history("rotate_xyphase", varlist)
+        print_log()
+        return
+
+    def swap_linears(self):
+        """
+        Swap the linear polarisations XX and YY
+        """
+        varlist = vars()
+        self._math._swap_linears(self)
+        self._add_history("swap_linears", varlist)
+        print_log()
+        return
+
+    def invert_phase(self):
+        """
+        Invert the phase of the complex polarisation
+        """
+        varlist = vars()
+        self._math._invert_phase(self)
+        self._add_history("invert_phase", varlist)
+        print_log()
+        return
+
+    def add(self, offset, insitu=None):
+        """
+        Return a scan where all spectra have the offset added
+        Parameters:
+            offset:      the offset
+            insitu:      if False a new scantable is returned.
+                         Otherwise, the scaling is done in-situ
+                         The default is taken from .asaprc (False)
+        """
+        if insitu is None: insitu = rcParams['insitu']
+        self._math._setinsitu(insitu)
+        varlist = vars()
+        s = scantable(self._math._unaryop(self, offset, "ADD", False))
+        s._add_history("add", varlist)
+        print_log()
+        if insitu:
+            self._assign(s)
+        else:
+            return s
+
+    def scale(self, factor, tsys=True, insitu=None, ):
+        """
+        Return a scan where all spectra are scaled by the give 'factor'
+        Parameters:
+            factor:      the scaling factor
+            insitu:      if False a new scantable is returned.
+                         Otherwise, the scaling is done in-situ
+                         The default is taken from .asaprc (False)
+            tsys:        if True (default) then apply the operation to Tsys
+                         as well as the data
+        """
+        if insitu is None: insitu = rcParams['insitu']
+        self._math._setinsitu(insitu)
+        varlist = vars()
+        s = scantable(self._math._unaryop(self, factor, "MUL", tsys))
+        s._add_history("scale", varlist)
+        print_log()
+        if insitu:
+            self._assign(s)
+        else:
+            return s
+
+    def auto_quotient(self, mode='time', preserve=True):
+        """
+        This function allows to build quotients automatically.
+        It assumes the observation to have the same numer of
+        "ons" and "offs"
+        It will support "closest off in time" in the future
+        Parameters:
+            mode:           the on/off detection mode; 'suffix' (default)
+                            'suffix' identifies 'off' scans by the
+                            trailing '_R' (Mopra/Parkes) or
+                            '_e'/'_w' (Tid)
+            preserve:       you can preserve (default) the continuum or
+                            remove it.  The equations used are
+                            preserve: Output = Toff * (on/off) - Toff
+                            remove:   Output = Toff * (on/off) - Ton
+        """
+        modes = ["time"]
+        if not mode in modes:
+            msg = "please provide valid mode. Valid modes are %s" % (modes)
+            raise ValueError(msg)
+        varlist = vars()
+        s = scantable(self._math._auto_quotient(self, mode, preserve))
+        s._add_history("auto_quotient", varlist)
+        print_log()
+        return s
+
+    def mx_quotient(self, mask = None, weight='median', preserve=True):
+        """
+        Form a quotient using "off" beams when observing in "MX" mode.
+        Parameters:
+            mask:           an optional mask to be used when weight == 'stddev'
+            weight:         How to average the off beams.  Default is 'median'.
+            preserve:       you can preserve (default) the continuum or
+                            remove it.  The equations used are
+                            preserve: Output = Toff * (on/off) - Toff
+                            remove:   Output = Toff * (on/off) - Ton
+	"""
+        if mask is None: mask = ()
+        varlist = vars()
+        on = scantable(self._math._mx_extract(self, 'on'))
+        preoff = scantable(self._math._mx_extract(self, 'off'))
+        off = preoff.average_time(mask=mask, weight=weight, scanav=False)
+	from asapmath  import quotient
+        q = quotient(on, off, preserve)
+        q._add_history("mx_quotient", varlist)
+        print_log()
+	return q
+
+    def freq_switch(self, insitu=None):
+        """
+        Apply frequency switching to the data.
+        Parameters:
+            insitu:      if False a new scantable is returned.
+                         Otherwise, the swictching is done in-situ
+                         The default is taken from .asaprc (False)
+        Example:
+            none
+        """
+        if insitu is None: insitu = rcParams['insitu']
+        self._math._setinsitu(insitu)
+        varlist = vars()
+        s = scantable(self._math._freqswitch(self))
+        s._add_history("freq_switch", varlist)
+        print_log()
+        if insitu: self._assign(s)
+        else: return s
+
+    def recalc_azel(self):
+        """
+        Recalculate the azimuth and elevation for each position.
+        Parameters:
+            none
+        Example:
+        """
+        varlist = vars()
+        self._recalcazel()
+        self._add_history("recalc_azel", varlist)
+        print_log()
+        return
+
+    def __add__(self, other):
+        varlist = vars()
+        s = None
+        if isinstance(other, scantable):
+            print "scantable + scantable NYI"
+            return
+        elif isinstance(other, float):
+            s = scantable(self._math._unaryop(self, other, "ADD", False))
+        else:
+            raise TypeError("Other input is not a scantable or float value")
+        s._add_history("operator +", varlist)
+        print_log()
+        return s
+
+    def __sub__(self, other):
+        """
+        implicit on all axes and on Tsys
+        """
+        varlist = vars()
+        s = None
+        if isinstance(other, scantable):
+            print "scantable - scantable NYI"
+            return
+        elif isinstance(other, float):
+            s = scantable(self._math._unaryop(self, other, "SUB", False))
+        else:
+            raise TypeError("Other input is not a scantable or float value")
+        s._add_history("operator -", varlist)
+        print_log()
+        return s
+
+    def __mul__(self, other):
+        """
+        implicit on all axes and on Tsys
+        """
+        varlist = vars()
+        s = None
+        if isinstance(other, scantable):
+            print "scantable * scantable NYI"
+            return
+        elif isinstance(other, float):
+            s = scantable(self._math._unaryop(self, other, "MUL", False))
+        else:
+            raise TypeError("Other input is not a scantable or float value")
+        s._add_history("operator *", varlist)
+        print_log()
+        return s
+
+
+    def __div__(self, other):
+        """
+        implicit on all axes and on Tsys
+        """
+        varlist = vars()
+        s = None
+        if isinstance(other, scantable):
+            print "scantable / scantable NYI"
+            return
+        elif isinstance(other, float):
+            if other == 0.0:
+                raise ZeroDivisionError("Dividing by zero is not recommended")
+            s = scantable(self._math._unaryop(self, other, "DIV", False))
+        else:
+            raise TypeError("Other input is not a scantable or float value")
+        s._add_history("operator /", varlist)
+        print_log()
+        return s
+
+    def get_fit(self, row=0):
+        """
+        Print or return the stored fits for a row in the scantable
+        Parameters:
+            row:    the row which the fit has been applied to.
+        """
+        if row > self.nrow():
+            return
+        from asap.asapfit import asapfit
+        fit = asapfit(self._getfit(row))
+        if rcParams['verbose']:
+            print fit
+            return
+        else:
+            return fit.as_dict()
+
+    def _add_history(self, funcname, parameters):
+        # create date
+        sep = "##"
+        from datetime import datetime
+        dstr = datetime.now().strftime('%Y/%m/%d %H:%M:%S')
+        hist = dstr+sep
+        hist += funcname+sep#cdate+sep
+        if parameters.has_key('self'): del parameters['self']
+        for k, v in parameters.iteritems():
+            if type(v) is dict:
+                for k2, v2 in v.iteritems():
+                    hist += k2
+                    hist += "="
+                    if isinstance(v2, scantable):
+                        hist += 'scantable'
+                    elif k2 == 'mask':
+                        if isinstance(v2, list) or isinstance(v2, tuple):
+                            hist += str(self._zip_mask(v2))
+                        else:
+                            hist += str(v2)
+                    else:
+                        hist += str(v2)
+            else:
+                hist += k
+                hist += "="
+                if isinstance(v, scantable):
+                    hist += 'scantable'
+                elif k == 'mask':
+                    if isinstance(v, list) or isinstance(v, tuple):
+                        hist += str(self._zip_mask(v))
+                    else:
+                        hist += str(v)
+                else:
+                    hist += str(v)
+            hist += sep
+        hist = hist[:-2] # remove trailing '##'
+        self._addhistory(hist)
+
+
+    def _zip_mask(self, mask):
+        mask = list(mask)
+        i = 0
+        segments = []
+        while mask[i:].count(1):
+            i += mask[i:].index(1)
+            if mask[i:].count(0):
+                j = i + mask[i:].index(0)
+            else:
+                j = len(mask)
+            segments.append([i, j])
+            i = j
+        return segments
+
+    def _get_ordinate_label(self):
+        fu = "("+self.get_fluxunit()+")"
+        import re
+        lbl = "Intensity"
+        if re.match(".K.", fu):
+            lbl = "Brightness Temperature "+ fu
+        elif re.match(".Jy.", fu):
+            lbl = "Flux density "+ fu
+        return lbl
+
+    def _check_ifs(self):
+        nchans = [self.nchan(i) for i in range(self.nif(-1))]
+        nchans = filter(lambda t: t > 0, nchans)
+        return (sum(nchans)/len(nchans) == nchans[0])
+
+    def _fill(self, names, unit, average):
+        import os
+        from asap._asap import stfiller
+        first = True
+        fullnames = []
+        for name in names:
+            name = os.path.expandvars(name)
+            name = os.path.expanduser(name)
+            if not os.path.exists(name):
+                msg = "File '%s' does not exists" % (name)
+                if rcParams['verbose']:
+                    asaplog.push(msg)
+                    print asaplog.pop().strip()
+                    return
+                raise IOError(msg)
+            fullnames.append(name)
+        if average:
+            asaplog.push('Auto averaging integrations')
+        stype = int(rcParams['scantable.storage'].lower() == 'disk')
+        for name in fullnames:
+            tbl = Scantable(stype)
+            r = stfiller(tbl)
+            msg = "Importing %s..." % (name)
+            asaplog.push(msg, False)
+            print_log()
+            r._open(name, -1, -1)
+            r._read()
+            #tbl = r._getdata()
+            if average:
+                tbl = self._math._average((tbl, ), (), 'NONE', 'SCAN')
+                #tbl = tbl2
+            if not first:
+                tbl = self._math._merge([self, tbl])
+                #tbl = tbl2
+            Scantable.__init__(self, tbl)
+            r._close()
+            del r, tbl
+            first = False
+        if unit is not None:
+            self.set_fluxunit(unit)
+        self.set_freqframe(rcParams['scantable.freqframe'])
+        #self._add_history("scantable", varlist)
+
Index: tags/Release2.1.0b/python/selector.py
===================================================================
--- tags/Release2.1.0b/python/selector.py	(revision 1196)
+++ tags/Release2.1.0b/python/selector.py	(revision 1196)
@@ -0,0 +1,181 @@
+from asap._asap import selector as _selector
+from asap import unique, _to_list
+
+class selector(_selector):
+    """
+    A selection object to be applied to scantables to restrict the
+    scantables to specific rows.
+    """
+    def __init(self):
+        _selector.__init__(self)
+
+    def reset(self):
+        """
+        Unset all selections.
+        """
+        self._reset()
+
+    def is_empty(self):
+        """
+        Has anything been set?
+        """
+        return self._empty()
+
+    def set_polarisations(self, pols=[]):
+        """
+        Set the polarisations to be selected in the scantable.
+        Parameters:
+             pols:     a list of integers of 0-3, or strings, e.g ["I","Q"].
+                       Default [] is no selection
+        Example:
+             sel = selector()
+             # These are equivalent if data is 'linear'
+             sel.set_polarisations(["XX","Re(XY)"])
+             sel.set_polarisations([0,2])
+             # reset the polarisation selection
+             sel.set_polarisations()
+
+        """
+        vec = _to_list(pols, str) or _to_list(pols, int)
+        if isinstance(vec, list): # is an empty and/or valid vector
+            if len(vec) and isinstance(vec[-1],str):
+                self._setpolstrings(vec)
+                return
+            self._setpols(vec)
+        else:
+            raise TypeError('Unknown pol type. Please use [0,1...] or ["XX","YY"...]')
+
+    def set_ifs(self, ifs=[]):
+        """
+        Set a sequence of IF numbers (0-based).
+        Parameters:
+            ifs:    a list of integers. Default [] is to unset the selection.
+        """
+        vec = _to_list(ifs, int)
+        if isinstance(vec,list):
+            self._setifs(vec)
+        else:
+            raise TypeError('Unknown IFno type. Use lists of integers.')
+
+    def set_scans(self, scans=[]):
+        """
+        Set a sequence of Scan numbers (0-based).
+        Parameters:
+            scans:    a list of integers. Default [] is to unset the selection.
+        """
+        vec = _to_list(scans, int)
+        if isinstance(vec,list):
+            self._setscans(vec)
+        else:
+            raise TypeError('Unknown Scan number type. Use lists of integers.')
+
+    def set_beams(self, beams=[]):
+        """
+        Set a sequence of Beam numbers (0-based).
+        Parameters:
+            beams:    a list of integers. Default [] is to unset the selection.
+        """
+        vec = _to_list(beams, int)
+        if isinstance(vec,list):
+            self._setbeams(vec)
+        else:
+            raise TypeError('Unknown Beam number type. Use lists of integers.')
+
+    def set_cycles(self, cycles=[]):
+        """
+        Set a sequence of IF numbers (0-based).
+        Parameters:
+            cycless:    a list of integers. Default [] is to unset the selection.
+        """
+        vec = _to_list(cycles, int)
+        if isinstance(vec,list):
+            self._setcycles(vec)
+        else:
+            raise TypeError('Unknown Cycle number type. Use lists of integers.')
+
+
+    def set_name(self, name):
+        """
+        Set a selection based on a name. This can be a unix pattern , e.g. "*_R"
+        Parameters:
+            name:    a string containing a source name or pattern
+        Examples:
+            # select all reference scans which start with "Orion"
+            selection.set_name("Orion*_R")
+        """
+        if isinstance(name, str):
+            self._setname(name)
+        else:
+            raise TypeError('name must be a string')
+
+    def set_tsys(self, tsysmin=0.0, tsysmax=None):
+        """
+        Select by Tsys range.
+        Parameters:
+            tsysmin:     the lower threshold. Default 0.0
+            tsysmax:     the upper threshold. Default None.
+        Examples:
+            # select all spectra with Tsys <= 500.0
+            selection.set_tsys(tsysmax=500.0)
+
+        """
+        taql =  "SELECT FROM $1 WHERE TSYS[0] >= %f" % (tsysmin)
+        if isinstance(tsysmax, float):
+            taql = taql + " AND TSYS[0] <= %f" % ( tsysmax)
+        self._settaql(taql)
+
+    def set_query(self, query):
+        """
+        Select by Column query. Power users only!
+        Example:
+            # select all off scans with integration times over 60 seconds.
+            selection.set_query("SRCTYPE == 1 AND INTERVAL > 60.0")
+        """
+        taql = "SELECT FROM $1 WHERE " + query
+        self._settaql(taql)
+
+    def set_order(self, order):
+        """
+        Set the order the scantable should be sorted by.
+        Parameters:
+            order:    The list of column names to sort by in order
+        """
+        self._setorder(order)
+
+    def get_scans(self):
+        return list(self._getscans())
+    def get_cycles(self):
+        return list(self._getcycles())
+    def get_beams(self):
+        return list(self._getbeams())
+    def get_ifs(self):
+        return list(self._getifs())
+    def get_pols(self):
+        return list(self._getpols())
+    def get_poltypes(self):
+        return list(self._getpoltypes())
+    def get_order(self):
+        return list(self._getorder())
+    def get_taql(self):
+        return self._gettaql()
+    def get_name(self):
+        print "NYI"
+        s = self._gettaql()
+        return ""
+
+    def __add__(self, other):
+        """
+        Merge two selections.
+        """
+        union = selector()
+        gets = [[self._getscans(), other._getscans(), union._setscans],
+                [self._getcycles(), other._getcycles(),union._setcycles],
+                [self._getbeams(), other._getbeams(), union._setbeams],
+                [self._getifs(), other._getifs(), union._setifs],
+                [self._getpols(), other._getpols(), union._setpols]]
+        for v in gets:
+            vec = list(v[0]+v[1])
+            vec.sort()
+            v[2](unique(vec))
+        union._settaql(other._gettaql())
+        return union
Index: tags/Release2.1.0b/scons/archiver.py
===================================================================
--- tags/Release2.1.0b/scons/archiver.py	(revision 1196)
+++ tags/Release2.1.0b/scons/archiver.py	(revision 1196)
@@ -0,0 +1,26 @@
+def make_archive(target, source, env):
+    from distutils.archive_util import make_tarball
+    make_tarball(str(target[0]), str(source[0]), compress='bzip2')
+    return 0
+
+def make_archive_str(target, source, env):
+    return "Creating archive %s.tar.bz2 from %s" % (target[0], source[0])
+
+def generate(env):
+    archiver_builder = env.Builder(
+        action = env.Action(make_archive,make_archive_str),
+        source_factory=env.fs.Entry,
+        target_factory=env.fs.Entry,
+        multi=0
+    )
+
+    env.Append(BUILDERS = {
+        'Archiver': archiver_builder
+    })
+
+    env.AppendUnique(
+        ARCHIVER = 'archiver',
+    )
+
+def exists(env):
+   return True
Index: tags/Release2.1.0b/scons/casa.py
===================================================================
--- tags/Release2.1.0b/scons/casa.py	(revision 1196)
+++ tags/Release2.1.0b/scons/casa.py	(revision 1196)
@@ -0,0 +1,62 @@
+import os
+import re
+import sys
+import platform
+
+def generate(env):
+    def CheckCasaLib(context, lib):
+        context.Message("Checking casa library '%s'..."%lib)
+
+        context.Result(r)
+        return r
+
+    def CheckCasa(context, path=None):
+        ''' look for casa libraries'''
+        def addCasa(env):
+            casalibs = "casav atnf images ms components coordinates \
+                        lattices fits measures measures_f \
+                        tables scimath scimath_f casa wcs".split()
+            env.Prepend( LIBS =  casalibs )
+            casaincd = [os.path.join(env['CASAROOT'], 'code/include'), \
+                        os.path.join(env['CASAROOT'], 'code/casa')]
+            env.Append( CPPPATH = casaincd )
+            casalibd = os.path.join(env['CASAROOT'], env['CASAARCH'], 'lib')
+            env.Append( LIBPATH = [ casalibd ] )
+            # Explicit templates in casa
+            env.Append( CPPFLAGS = ['-DAIPS_NO_TEMPLATE_SRC'] )
+        context.Message('Checking for casa libraries...')
+        casaarch = None
+        if os.environ.has_key('AIPSPATH'):
+            casa = os.environ.get('AIPSPATH').split()
+            context.env.Append(CASAARCH = casa[1])
+            context.env.Append(CASAROOT = casa[0])
+            addCasa(context.env)
+            context.Result('yes')
+            return True
+        casaarch = 'linux_gnu'
+        if context.env["PLATFORM"] == 'darwin':
+            casaarch = 'darwin'
+        elif sys.platform == 'linux2' and platform.architecture()[0] == '64bit':
+            casaarch = 'linux_64b'
+        paths = "/nfs/aips++/weekly /aips++ /opt/aips++ ../casa_asap".split()
+        if path is not None and len(path):
+            paths = [path]
+        # @todo poor mans detection, do autocontext later
+        for p in paths:
+            if os.path.isfile(os.path.join(p, casaarch, "lib/libcasa.a")):
+                context.env.Append(CASAARCH = casaarch)
+                context.env.Append(CASAROOT = os.path.abspath(p))
+                addCasa(context.env)
+                context.Result('yes')
+                return True
+        context.Result('no')
+        return False
+
+
+    def AddCasaTest(conf):
+        conf.AddTests({'CheckCasa': CheckCasa})
+
+    env.AddCasaTest = AddCasaTest
+
+def exists(env):
+    return true
Index: tags/Release2.1.0b/scons/quietinstall.py
===================================================================
--- tags/Release2.1.0b/scons/quietinstall.py	(revision 1196)
+++ tags/Release2.1.0b/scons/quietinstall.py	(revision 1196)
@@ -0,0 +1,38 @@
+import SCons
+
+def no_output(target, source, env):
+    return ""
+
+def generate(env):
+
+    def QInstall(dir, source):
+        dnodes = env.arg2nodes(dir, env.fs.Dir)
+        sources = env.arg2nodes(source, env.fs.File)
+        tgt = []
+        for dnode in dnodes:
+            for src in sources:
+                target = env.fs.File(src.name, dnode)
+                tgt.extend(env.QuietInstaller(target, src))
+        return tgt
+    env.QInstall = QInstall
+    
+    def QInstallAs(target, source):
+        result = []
+        sources = env.arg2nodes(source, env.fs.File)
+        targets = env.arg2nodes(target, env.fs.File)
+        for src, tgt in map(lambda x, y: (x, y), sources, targets):
+            result.extend(env.QuietInstaller(tgt, src))
+
+    env.QInstallAs = QInstallAs
+    
+    quietinstaller_builder = env.Builder(
+        action = env.Action(SCons.Environment.installFunc, no_output),
+        multi=1
+    )
+
+    env.Append(BUILDERS = {
+        'QuietInstaller': quietinstaller_builder
+    })
+
+def exists(env):
+   return True
Index: tags/Release2.1.0b/scons/utils.py
===================================================================
--- tags/Release2.1.0b/scons/utils.py	(revision 1196)
+++ tags/Release2.1.0b/scons/utils.py	(revision 1196)
@@ -0,0 +1,44 @@
+import os
+import glob
+
+def generate(env):
+    def SGlob(pattern):
+        path = env.GetBuildPath('SConscript').replace('SConscript', '')
+        return [ i.replace(path, '') for i in glob.glob(path + pattern) ]
+    env.SGlob = SGlob
+
+    def AddCustomPath(path=""):
+        if not len(path) or not os.path.exists(path):
+            return
+        env.PrependUnique(CPPPATH = [os.path.join(path, "include")])
+        env.PrependUnique(LIBPATH = [os.path.join(path, "lib")])
+    env.AddCustomPath = AddCustomPath
+
+    def WalkDirTree(targetroot, sourceroot, sources):
+        ifiles = []
+        ofiles = []
+        for s in sources:
+            if os.path.isdir(os.path.join(sourceroot ,s)):
+                for d,ld,f in os.walk(os.path.join(sourceroot ,s)):
+                    for fl in f:
+                        ifile = os.path.join(d, fl)
+                        ifiles.append(ifile)
+                        ofile = ifile.replace(sourceroot, targetroot)
+                        ofiles.append(ofile)
+        return ofiles, ifiles
+    env.WalkDirTree = WalkDirTree
+
+    def null_action(target, source, env): return 0
+
+    def message(target, source, env):
+        return "%s" % target[0]
+    env.MessageAction = env.Action(null_action, message)
+
+def exists(env):
+    try:
+        import os
+        import glob
+    except ImportError:
+        return False
+    else:
+        return True
Index: tags/Release2.1.0b/share/ipythonrc-asap
===================================================================
--- tags/Release2.1.0b/share/ipythonrc-asap	(revision 1196)
+++ tags/Release2.1.0b/share/ipythonrc-asap	(revision 1196)
@@ -0,0 +1,385 @@
+# -*- Mode: Shell-Script -*-  Not really, but shows comments correctly
+#***************************************************************************
+#
+# Configuration file for ipython -- ipythonrc format
+#
+# The format of this file is one of 'key value' lines.
+# Lines containing only whitespace at the beginning and then a # are ignored
+# as comments. But comments can NOT be put on lines with data.
+#***************************************************************************
+autocall 1
+
+autoindent 1
+
+automagic 1
+
+# Size of the output cache. After this many entries are stored, the cache will
+# get flushed. Depending on the size of your intermediate calculations, you
+# may have memory problems if you make it too big, since keeping things in the
+# cache prevents Python from reclaiming the memory for old results. Experiment
+# with a value that works well for you.
+
+# If you choose cache_size 0 IPython will revert to python's regular >>>
+# unnumbered prompt. You will still have _, __ and ___ for your last three
+# results, but that will be it.  No dynamic _1, _2, etc. will be created. If
+# you are running on a slow machine or with very limited memory, this may
+# help.
+
+cache_size 100
+
+# Currently available schemes: NoColor, Linux, LightBG.
+
+# keep uncommented only the one you want:
+colors Linux
+#colors LightBG
+#colors NoColor
+
+# color_info: IPython can display information about objects via a set of
+# functions, and optionally can use colors for this, syntax highlighting
+# source code and various other elements. This information is passed through a
+# pager (it defaults to 'less' if $PAGER is not set). 
+
+# If your pager has problems, try to setting it to properly handle escapes
+# (see the less manpage for detail), or disable this option.  The magic
+# function @color_info allows you to toggle this interactively for testing.
+
+color_info 1
+
+# confirm_exit: set to 1 if you want IPython to confirm when you try to exit
+# with an EOF (Control-d in Unix, Control-Z/Enter in Windows). Note that using
+# the magic functions @Exit or @Quit you can force a direct exit, bypassing
+# any confirmation.
+
+confirm_exit 1
+
+# Use deep_reload() as a substitute for reload() by default. deep_reload() is
+# still available as dreload() and appears as a builtin.
+
+deep_reload 0
+
+# Which editor to use with the @edit command. If you leave this at 0, IPython
+# will honor your EDITOR environment variable. Since this editor is invoked on
+# the fly by ipython and is meant for editing small code snippets, you may
+# want to use a small, lightweight editor here.
+
+# For Emacs users, setting up your Emacs server properly as described in the
+# manual is a good idea. An alternative is to use jed, a very light editor
+# with much of the feel of Emacs (though not as powerful for heavy-duty work).
+
+editor 0
+
+# log 1 -> same as ipython -log. This automatically logs to ./ipython.log
+log 0
+
+# Same as ipython -Logfile YourLogfileName. 
+# Don't use with log 1 (use one or the other)
+logfile ''
+
+# banner 0 -> same as ipython -nobanner
+banner 0
+
+# messages 0 -> same as ipython -nomessages
+messages 0
+
+# Automatically call the pdb debugger after every uncaught exception. If you
+# are used to debugging using pdb, this puts you automatically inside of it
+# after any call (either in IPython or in code called by it) which triggers an
+# exception which goes uncaught.
+pdb 0
+
+# Enable the pprint module for printing. pprint tends to give a more readable
+# display (than print) for complex nested data structures.
+pprint 1
+
+# Prompt strings (see ipython --help for more details).
+# Use %n to represent the current prompt number, and quote them to protect
+# spaces.
+prompt_in1 'ASAP>'
+
+# In prompt_in2, %n is replaced by as many dots as there are digits in the
+# current value of %n.
+prompt_in2 '   .%n.:'
+
+prompt_out 'asap>:'
+
+# quick 1 -> same as ipython -quick
+quick 0
+
+# Use the readline library (1) or not (0). Most users will want this on, but
+# if you experience strange problems with line management (mainly when using
+# IPython inside Emacs buffers) you may try disabling it. Not having it on
+# prevents you from getting command history with the arrow keys, searching and
+# name completion using TAB.
+
+readline 1
+
+# Screen Length: number of lines of your screen. This is used to control
+# printing of very long strings. Strings longer than this number of lines will
+# be paged with the less command instead of directly printed.
+
+# The default value for this is 0, which means IPython will auto-detect your
+# screen size every time it needs to print. If for some reason this isn't
+# working well (it needs curses support), specify it yourself. Otherwise don't
+# change the default.
+
+screen_length 0
+
+# Prompt separators for input and output.
+# Use \n for newline explicitly, without quotes.
+# Use 0 (like at the cmd line) to turn off a given separator.
+
+# The structure of prompt printing is:
+# (SeparateIn)Input....
+# (SeparateOut)Output...
+# (SeparateOut2),   # that is, no newline is printed after Out2
+# By choosing these you can organize your output any way you want.
+
+separate_in \n
+
+separate_out 0
+
+separate_out2 0
+
+# 'nosep 1' is a shorthand for '-SeparateIn 0 -SeparateOut 0 -SeparateOut2 0'.
+# Simply removes all input/output separators, overriding the choices above.
+nosep 0
+
+# xmode - Exception reporting mode. 
+
+# Valid modes: Plain, Context and Verbose.
+
+# Plain: similar to python's normal traceback printing.
+
+# Context: prints 5 lines of context source code around each line in the
+# traceback.
+
+# Verbose: similar to Context, but additionally prints the variables currently
+# visible where the exception happened (shortening their strings if too
+# long). This can potentially be very slow, if you happen to have a huge data
+# structure whose string representation is complex to compute. Your computer
+# may appear to freeze for a while with cpu usage at 100%. If this occurs, you
+# can cancel the traceback with Ctrl-C (maybe hitting it more than once).
+
+xmode Plain
+#xmode Context
+#xmode Verbose
+
+#---------------------------------------------------------------------------
+# Section: Readline configuration (readline is not available for MS-Windows)
+
+# This is done via the following options:
+
+# (i) readline_parse_and_bind: this option can appear as many times as you
+# want, each time defining a string to be executed via a
+# readline.parse_and_bind() command. The syntax for valid commands of this
+# kind can be found by reading the documentation for the GNU readline library,
+# as these commands are of the kind which readline accepts in its
+# configuration file.
+
+# The TAB key can be used to complete names at the command line in one of two
+# ways: 'complete' and 'menu-complete'. The difference is that 'complete' only
+# completes as much as possible while 'menu-complete' cycles through all
+# possible completions. Leave the one you prefer uncommented.
+
+readline_parse_and_bind tab: complete
+#readline_parse_and_bind tab: menu-complete
+
+# This binds Control-l to printing the list of all possible completions when
+# there is more than one (what 'complete' does when hitting TAB twice, or at
+# the first TAB if show-all-if-ambiguous is on)
+readline_parse_and_bind "\C-l": possible-completions
+
+# This forces readline to automatically print the above list when tab
+# completion is set to 'complete'. You can still get this list manually by
+# using the key bound to 'possible-completions' (Control-l by default) or by
+# hitting TAB twice. Turning this on makes the printing happen at the first
+# TAB.
+readline_parse_and_bind set show-all-if-ambiguous on
+
+# If you have TAB set to complete names, you can rebind any key (Control-o by
+# default) to insert a true TAB character.
+readline_parse_and_bind "\C-o": tab-insert
+
+# These commands allow you to indent/unindent easily, with the 4-space
+# convention of the Python coding standards.  Since IPython's internal
+# auto-indent system also uses 4 spaces, you should not change the number of
+# spaces in the code below.
+readline_parse_and_bind "\M-i": "    "
+readline_parse_and_bind "\M-o": "\d\d\d\d"
+readline_parse_and_bind "\M-I": "\d\d\d\d"
+
+# Bindings for incremental searches in the history. These searches use the
+# string typed so far on the command line and search anything in the previous
+# input history containing them.
+readline_parse_and_bind "\C-r": reverse-search-history
+readline_parse_and_bind "\C-s": forward-search-history
+
+# Bindings for completing the current line in the history of previous
+# commands. This allows you to recall any previous command by typing its first
+# few letters and hitting Control-p, bypassing all intermediate commands which
+# may be in the history (much faster than hitting up-arrow 50 times!)
+readline_parse_and_bind "\C-p": history-search-backward
+readline_parse_and_bind "\C-n": history-search-forward
+
+# (ii) readline_remove_delims: a string of characters to be removed from the
+# default word-delimiters list used by readline, so that completions may be
+# performed on strings which contain them.
+
+readline_remove_delims '"[]{}-/~
+
+#"' -- just to fix emacs coloring which gets confused by unmatched quotes.
+
+# (iii) readline_omit__names: normally hitting <tab> after a '.' in a name
+# will complete all attributes of an object, including all the special methods
+# whose names inlclude double underscores (like __getitem__ or __class__). If
+# you'd rather not see these names by default, you can set this option to 1.
+
+# Note that even when this option is set, you can still see those names by
+# explicitly typing a _ after the period and hitting <tab>: 'name._<tab>' will
+# always complete attribute names starting with '_'.
+
+# This option is off by default so that new users see all attributes of any
+# objects they are dealing with.
+
+readline_omit__names 0
+
+#---------------------------------------------------------------------------
+# Section: modules to be loaded with 'import ...'
+
+# List, separated by spaces, the names of the modules you want to import
+
+# Example:
+# import_mod sys os
+# will produce internally the statements
+# import sys
+# import os
+
+# Each import is executed in its own try/except block, so if one module
+# fails to load the others will still be ok.
+
+import_mod sys
+
+#---------------------------------------------------------------------------
+# Section: modules to import some functions from: 'from ... import ...'
+
+# List, one per line, the modules for which you want only to import some
+# functions. Give the module name first and then the name of functions to be
+# imported from that module.
+
+# Example:
+# import_some struct pack unpack
+# will produce internally the statement
+# from struct import pack,unpack
+
+# If you have more than one modules_some line, each gets its own try/except
+# block (like modules, see above).
+
+import_some 
+
+#---------------------------------------------------------------------------
+# Section: modules to import all from : 'from ... import *'
+
+# List (same syntax as import_mod above) those modules for which you want to
+# import all functions. Remember, this is a potentially dangerous thing to do,
+# since it is very easy to overwrite names of things you need. Use with
+# caution.
+
+# Example:
+# import_all sys os
+# will produce internally the statements
+# from sys import *
+# from os import *
+
+# As before, each will be called in a separate try/except block.
+
+import_all asap
+
+#---------------------------------------------------------------------------
+# Section: Python code to execute.
+
+# Put here code to be explicitly executed (keep it simple!)
+# Put one line of python code per line. All whitespace is removed (this is a
+# feature, not a bug), so don't get fancy building loops here.
+# This is just for quick convenient creation of things you want available.
+
+# Example:
+# banner 0 disables  sys.stdout
+execute sys.stdout = sys.__stdout__
+# and each *line* (not each statement, we don't do python syntax parsing) is
+# executed in its own try/except block.
+execute print welcome()
+
+# Note for the adventurous: you can use this to define your own names for the
+# magic functions, by playing some namespace tricks:
+
+# execute __IP.magic_cl = __IP.magic_clear
+
+# defines @cl as a new name for @clear.
+
+#---------------------------------------------------------------------------
+# Section: Pyhton files to load and execute.
+
+# Put here the full names of files you want executed with execfile(file).  If
+# you want complicated initialization, just write whatever you want in a
+# regular python file and load it from here.
+
+# Filenames defined here (which *must* include the extension) are searched for
+# through all of sys.path. Since IPython adds your .ipython directory to
+# sys.path, they can also be placed in your .ipython dir and will be
+# found. Otherwise (if you want to execute things not in .ipyton nor in
+# sys.path) give a full path (you can use ~, it gets expanded)
+
+# Example:
+# execfile file1.py ~/file2.py
+# will generate
+# execfile('file1.py')
+# execfile('_path_to_your_home/file2.py')
+
+# As before, each file gets its own try/except block.
+
+execfile asapuserfuncs.py
+
+# If you are feeling adventurous, you can even add functionality to IPython
+# through here. IPython works through a global variable called __ip which
+# exists at the time when these files are read. If you know what you are doing
+# (read the source) you can add functions to __ip in files loaded here. 
+
+# The file example-magic.py contains a simple but correct example. Try it:
+
+# execfile example-magic.py
+
+# Look at the examples in IPython/iplib.py for more details on how these magic
+# functions need to process their arguments.
+
+#---------------------------------------------------------------------------
+# Section: aliases for system shell commands
+
+# Here you can define your own names for system commands. The syntax is
+# similar to that of the builtin @alias function:
+
+# alias alias_name command_string
+
+# The resulting aliases are auto-generated magic functions (hence usable as
+# @alias_name)
+
+# For example:
+
+# alias myls ls -la
+
+# will define '@myls' as an alias for executing the system command 'ls -la'.
+# If automagic is on, you can just type myls like you would at a system shell
+# prompt.  This allows you to customize IPython's environment to have the same
+# aliases you are accustomed to from your own shell.
+
+# You can also define aliases with parameters using %s specifiers (one per
+# parameter):
+
+# alias parts echo first %s second %s
+
+# will give you in IPython:
+# >>> @parts A B
+# first A second B
+
+# Use one 'alias' statement per alias you wish to define.
+
+#alias
Index: tags/Release2.1.0b/src/Factory.h
===================================================================
--- tags/Release2.1.0b/src/Factory.h	(revision 1196)
+++ tags/Release2.1.0b/src/Factory.h	(revision 1196)
@@ -0,0 +1,38 @@
+//
+// C++ Interface: Factory
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <Malte.Marquarding@csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPFACTORY_H
+#define ASAPFACTORY_H
+
+#include "FactoryBase.h"
+
+namespace asap
+{
+
+/**
+A templated factory pattern
+
+@author Malte Marquarding
+@date $Date:$
+
+*/
+
+template <class BT,class DT>
+class Factory : public FactoryBase<BT> {
+public:
+    Factory() {}
+    virtual ~Factory() {}
+    virtual BT* create() { return new DT;}
+};
+
+} //namespace
+
+#endif
Index: tags/Release2.1.0b/src/FactoryBase.h
===================================================================
--- tags/Release2.1.0b/src/FactoryBase.h	(revision 1196)
+++ tags/Release2.1.0b/src/FactoryBase.h	(revision 1196)
@@ -0,0 +1,31 @@
+//
+// C++ Interface: FactoryBase
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <Malte.Marquarding@csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPFACTORYBASE_H
+#define ASAPFACTORYBASE_H
+
+namespace asap {
+/**
+A templated factory pattern
+
+@author Malte Marquarding
+@date $Date:$
+
+*/
+template <class BT>
+class FactoryBase {
+public:
+    FactoryBase() {}
+    virtual ~FactoryBase() {}
+    virtual BT* create() = 0;
+};
+} //namespace
+#endif
Index: tags/Release2.1.0b/src/LineCatalog.cpp
===================================================================
--- tags/Release2.1.0b/src/LineCatalog.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/LineCatalog.cpp	(revision 1196)
@@ -0,0 +1,132 @@
+
+//
+// C++ Implementation: LineCatalog
+//
+// Description: A class representing a line catalog
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+
+// std includes
+
+// casa includes
+#include <casa/Exceptions/Error.h>
+#include <casa/iostream.h>
+#include <casa/iomanip.h>
+#include <casa/OS/Path.h>
+#include <casa/OS/File.h>
+#include <casa/Arrays/Vector.h>
+#include <tables/Tables/ReadAsciiTable.h>
+#include <tables/Tables/TableParse.h>
+#include <tables/Tables/ScalarColumn.h>
+
+#include "STAttr.h"
+#include "LineCatalog.h"
+
+using namespace casa;
+
+namespace asap
+{
+
+LineCatalog::LineCatalog(const std::string& name)
+{
+  Path path(name);
+  std::string inname = path.expandedName();
+  File f(inname);
+  if (f.isDirectory()) { //assume it's a table
+    table_ = Table(inname);
+  } else {
+    String formatString;
+    // formatSring , TableType, ascii file name, TableDesc name, table name, autoheader
+    table_ = readAsciiTable(formatString, Table::Plain, inname, "", "", True);
+    // do not keep aips++ table
+    table_.markForDelete();
+  }
+  baseTable_ = table_;
+}
+
+void LineCatalog::setStrengthLimits(double smin, double smax)
+{
+  table_ = setLimits(smin, smax, "Column4");
+}
+
+void LineCatalog::setFrequencyLimits(double fmin, double fmax)
+{
+  table_ = setLimits(fmin, fmax, "Column2");
+}
+
+void LineCatalog::setPattern(const std::string& name, const std::string& stype)
+{
+  //std::string mode = stype+"('"+name+"')";
+  std::string taql = "SELECT FROM $1 WHERE Column1 == pattern('"+name+"')";
+  cerr << taql << endl;
+  Table tmp = tableCommand(taql, table_);
+  if (tmp.nrow() > 0) table_ = tmp.sort("Column2");
+  else throw(AipsError("No match."));
+}
+
+Table LineCatalog::setLimits(double lmin, double lmax, const std::string& colname)
+{
+  Table tmp = table_(table_.col(colname) > lmin && table_.col(colname) < lmax);
+  if (tmp.nrow() > 0) return tmp.sort("Column2");
+  else throw(AipsError("No match."));
+}
+
+void LineCatalog::save(const std::string& name)
+{
+  Path path(name);
+  std::string inname = path.expandedName();
+  table_.deepCopy(inname, Table::New);
+}
+
+std::string LineCatalog::summary(int row) const
+{
+  std::string stlout;
+  ostringstream oss;
+  oss << asap::SEPERATOR << endl;
+  oss << "Line Catalog summary" << endl;
+  oss << asap::SEPERATOR << endl << endl;
+  if (row == -1) {
+    for (uint i=0; i<table_.nrow(); ++i) {
+      oss << std::right << setw(7) << i << setw(2) << "";
+      oss << std::left << setw(20) << getName(i);
+      oss << setw(12) << setprecision(8) << std::left << getFrequency(i);
+      oss << endl;
+    }
+  } else {
+      if ( row < table_.nrow() ) {
+        oss << std::right << setw(7) << row << setw(2) << "";
+        oss << std::left << setw(20) << getName(row);
+        oss << setw(12) << setprecision(8) << std::left << getFrequency(row);
+        oss << endl;
+      } else {
+        throw(AipsError("Row doesn't exist"));
+      }
+  }
+  /// @todo implement me
+  return String(oss);
+}
+
+/*!
+    \fn asap::LineCatalog::getName(int row)
+ */
+std::string LineCatalog::getName(uint row) const
+{
+  ROScalarColumn<String> col(table_, "Column1");
+  return col(row);
+}
+
+double asap::LineCatalog::getFrequency(uint row) const
+{
+  ROScalarColumn<Double> col(table_, "Column2");
+  return col(row);
+}
+
+
+} // namespace
+
+
Index: tags/Release2.1.0b/src/LineCatalog.h
===================================================================
--- tags/Release2.1.0b/src/LineCatalog.h	(revision 1196)
+++ tags/Release2.1.0b/src/LineCatalog.h	(revision 1196)
@@ -0,0 +1,115 @@
+//
+// C++ Interface: LineCatalog
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <Malte.Marquarding@csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef LINECATALOG_H
+#define LINECATALOG_H
+
+#include <string>
+
+#include <casa/aips.h>
+#include <tables/Tables/Table.h>
+
+namespace asap {
+/**
+  * A represenation of a line catalog, which can be ASCII, or an aips++ table
+  *
+  * ASCII catalogs have to be formatted like JPL.
+  * Name frequency error log(I)
+  * Only  "name", "frequency" and log(I) are used at this stage
+  *
+  * @author Malte Marquarding
+  * @date $Date:$
+  */
+class LineCatalog {
+public:
+  /**
+    *
+    * @param name the name of the ASCII file or aips++ table
+    */
+  LineCatalog(const std::string& name = "jpl");
+  /**
+    * select a subset of the table by frequency range
+    * @param fmin the lower frequency bound
+    * @param fmin the upper frequency bound
+    */
+
+  virtual ~LineCatalog() {}
+
+  /**
+   * select a subset of the data by frequency range
+   * @param fmin the lower frequency bound
+   * @param fmax the upper frequency bound
+   */
+  void setFrequencyLimits(double fmin, double fmax);
+  /**
+    * select a subset of the table by line strength range
+    * @param smin the lower strength bound
+    * @param smin the upper strength bound
+    */
+  void setStrengthLimits(double smin, double smax);
+  /**
+    * select a subset of the data by name pattern match (unix-style)
+    * @param name the string pattern e.g. "*CS*"
+    * @param ptype pattern type e.g.
+    * @li "pattern"
+    * @li "regex"
+    */
+  void setPattern(const std::string& name, const std::string& ptype="pattern");
+  /**
+    * save the table  with current limits to disk (as an aips++ table)
+    * @param name the filename
+    */
+  void save(const std::string& name);
+  /**
+    * Return a string representation of this table
+    * @param row an integer describing the row number to show
+    * default -1 is all rows
+    * @return std::string
+    */
+  std::string summary(int row=-1) const;
+
+  /**
+   * Return the rest frequency value for a specific row
+   * @param row the row number
+   * @return a double rest frequency value
+   */
+  double getFrequency(uint row) const;
+
+  /**
+   *
+   * @param row
+   * @return
+   */
+  std::string getName(uint row) const;
+
+  int nrow() const  { return table_.nrow(); }
+
+  void reset() { table_ = baseTable_; }
+
+private:
+  /**
+   * utility function to handle range limits
+   * @param lmin the lower limit
+   * @param lmax the upper limit
+   * @param colname the columd to apply the limits to
+   * @return a new casa::Table
+   */
+  casa::Table setLimits(double lmin, double lmax, const std::string& colname);
+
+  // the table with seelection
+  casa::Table table_;
+  // the pristine table
+  casa::Table baseTable_;
+};
+
+} // namespace
+
+#endif //LINECATALOG_H
Index: tags/Release2.1.0b/src/Logger.cpp
===================================================================
--- tags/Release2.1.0b/src/Logger.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/Logger.cpp	(revision 1196)
@@ -0,0 +1,40 @@
+#include "Logger.h"
+
+using namespace asap;
+
+std::string Logger::log_ = std::string("");
+
+Logger::Logger()
+{
+  enableLog();
+}
+
+Logger::Logger(bool enabled)
+{
+  enabled_ = enabled;
+}
+Logger::~Logger()
+{
+}
+
+void Logger::pushLog(const std::string& s, bool newline ) const
+{
+  if (enabled_) {
+    log_+=s;
+    if ( newline ) log_+="\n";
+  };
+}
+std::string Logger::popLog() const
+{
+  std::string out;
+  if (enabled_) out=log_;log_="";
+  return out;
+}
+void Logger::enableLog()
+{
+  enabled_ = true;
+}
+void Logger::disableLog()
+{
+  enabled_ = false;
+}
Index: tags/Release2.1.0b/src/Logger.h
===================================================================
--- tags/Release2.1.0b/src/Logger.h	(revision 1196)
+++ tags/Release2.1.0b/src/Logger.h	(revision 1196)
@@ -0,0 +1,72 @@
+//
+// C++ Interface: Logger
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2005
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPLOGGER_H
+#define ASAPLOGGER_H
+
+#include <string>
+#include <iostream>
+
+namespace asap {
+/**
+  * This class provides the logging within asap. All other classes which need to log
+  * should inherit from this.
+  * @brief The ASAP logging class
+  * @author Malte Marquarding
+  * @date $Date$
+  * @version
+  */
+class Logger {
+public:
+  /**
+   * Default Constructor
+   **/
+  Logger();
+  
+  /**
+   * Constructor with switch to enable/disable logging
+   * @param[in] enabled indicating the deafult state
+   */  
+  Logger(bool enabled);
+
+  /*
+   * Destructor
+   */
+  virtual ~Logger();
+  /**
+   * push another message into the logger
+   * @param[in] s the message
+   * @param[in] newline whether to add a newline character at the end
+   */  
+  void pushLog(const std::string& s, bool newline=true) const;
+  /**
+   * pop the message form the logger
+   * @return the log message string
+   */  
+  std::string popLog() const;
+  /**
+   * enable logging
+   */
+  virtual void enableLog();
+  /**
+   * disable logging
+   */
+  virtual void disableLog();
+  
+private:
+  static std::string log_;
+  bool enabled_;
+};
+
+} // namespace
+
+#endif
+
Index: tags/Release2.1.0b/src/Makefile
===================================================================
--- tags/Release2.1.0b/src/Makefile	(revision 1196)
+++ tags/Release2.1.0b/src/Makefile	(revision 1196)
@@ -0,0 +1,198 @@
+TARGET   := /tmp/_asap.so
+
+# the casa environment AIPSPATH has to be defined
+CASAROOT  := $(word 1, $(AIPSPATH))
+CASAARCH  := $(word 2, $(AIPSPATH))
+CASAINC   := -I$(CASAROOT)/code/include -I$(CASAROOT)/code/casa
+CASALIB   := $(CASAROOT)/$(CASAARCH)/lib
+
+
+# the compiler
+ifndef CXX
+   CXX      := g++
+endif
+
+# compiler flags
+CXXFLAGS := -fPIC -O3
+CXXFLAGS += -ansi -pedantic -Wno-long-long -Wall
+CXXOPTS := -DAIPS_NO_TEMPLATE_SRC
+
+# darwin specific flags
+ifeq "$(CASAARCH)" "darwin"
+   CXXFLAGS := -O2 -DAIPS_DARWIN
+   CXXFLAGS += -ansi -pedantic -Wno-long-long -Wno-long-double
+endif
+
+
+# cfitsio
+CFITSIOROOT := /usr/local
+# If aips++ is build with LFS you need to have
+# libcfitsio with lfs support
+CFITSIOINC := -I$(CFITSIOROOT)/include
+CFITSIOLIB := $(CFITSIOROOT)/lib/libcfitsio_lfs.a
+#CFITSIOLIB := -L$(CFITSIOROOT) -lcfitsio
+
+#rpfits
+RPFITSROOT := /usr/local
+RPFITSLIB := $(RPFITSROOT)/lib/librpfits.a
+
+# This assumes all casa libs are static only (*.a)
+# if not than there might by symbol resolution errors.
+CASAPPLIB := $(CASALIB)/version.o \
+             -L$(CASALIB) \
+             -latnf -limages -lms -lcomponents -lcoordinates \
+             -llattices -lfits -lmeasures -lmeasures_f \
+             -ltables -lscimath -lscimath_f -lcasa  \
+             $(CASALIB)/libwcs.a \
+             $(RPFITSLIB) $(CFITSIOLIB) -lg2c -lstdc++
+
+# darwin specific CASA flags
+ifeq "$(CASAARCH)" "darwin"
+   CASAPPLIB += -framework vecLib
+else
+   CASAPPLIB += -llapack -lblas
+endif
+
+# the linker
+LD        := $(CXX)
+
+LDFLAGS   := -shared -Wl$(TARGET) -s
+ifeq "$(CASAARCH)" "darwin"
+   LDFLAGS   := -dynamiclib -single_module
+endif
+
+# python only 2.3 has been tested
+PYVERSION := 2.3
+PYTHONROOT := /usr
+PYTHONINC := -I$(PYTHONROOT)/include/python$(PYVERSION)
+PYTHONLIB := -L$(PYTHONROOT)/lib -lpython$(PYVERSION)
+
+# has to be build with same g++ version as casa
+BOOSTROOT := /usr
+BOOSTLIB  := -L$(BOOSTROOT)/lib -lboost_python
+BOOSTINC  := -I$(BOOSTROOT)/include/boost
+
+
+# DO NOT MODIFY AFTER THIS
+INCDIRS   := -I. $(CASAINC)  $(CFITSIOINC) $(BOOSTINC) $(PYTHONINC)
+LIBS      := $(PYTHONLIB)  $(BOOSTLIB) $(CASAPPLIB)
+
+CCOBJECTS := MathUtils.o \
+             MathUtils2.o \
+             RowAccumulator.o \
+             Logger.o \
+             STAttr.o \
+             STHeader.o \
+             STFiller.o \
+             STMath.o \
+             STSubTable.o \
+             STMolecules.o \
+             STTcal.o \
+             STFocus.o \
+             STWeather.o \
+             STFrequencies.o \
+             STHistory.o \
+             STSelector.o \
+	     STLineFinder.o \
+             STFitter.o \
+             STFitEntry.o \
+             STFit.o \
+             STPol.o \
+             STPolLinear.o \
+             STPolStokes.o \
+             STWriter.o \
+             STAsciiWriter.o \
+             Scantable.o \
+             Templates.o 
+PYOBJECTS := python_Scantable.o \
+             python_STFiller.o \
+             python_STSelector.o \
+             python_STMath.o \
+             python_Fitter.o \
+             python_Logger.o \
+             python_STLineFinder.o \
+             python_STFitEntry.o \
+             python_STWriter.o \
+	     python_asap.o
+
+OBJECTS := $(CCOBJECTS) $(PYOBJECTS)
+
+HEADERS   := MathUtils.h \
+             Logger.h \
+             STAttr.h \
+             STHeader.h \
+             Scantable.h \
+             STFiller.h \
+             STMath.h \
+             RowAccumulator.h \
+             STSubTable.h \
+             STMolecules.h \
+             STTcal.h \
+             STFocus.h \
+             STWeather.h \
+             STHistory.h \
+             STFrequencies.h \
+             STDefs.h \
+             STWriter.h \
+             STSelector.h \
+	     STFitter.h \
+	     STFit.h \
+	     STFitEntry.h \
+	     STLineFinder.h \
+             Factory.h \
+             FactoryBase.h \
+	     STPol.h \
+	     STPolStokes.h \
+	     STPolLinear.h \
+	     STWriter.h \
+	     STAsciiWriter.h
+
+STATICCCLIB := libasap.a
+MEMBERS := $(patsubst %,$(STATICCCLIB)(%),$(CCOBJECTS))
+
+.SUFFIXES: .c .o .cpp .cc .cxx .C
+
+.cpp.o:
+	-@ echo
+	  $(CXX) $(CXXOPTS) -c $(CXXFLAGS) $(INCDIRS) -o $@ $<
+
+.cc.o:
+	-@ echo
+	  $(CXX) $(CXXOPTS) -c $(CXXFLAGS) $(INCDIRS) -o $@ $<
+
+.cxx.o:
+	-@ echo
+	  $(CXX) $(CXXOPTS) -c $(CXXFLAGS) $(INCDIRS) -o $@ $<
+
+.C.o:
+	-@ echo
+	  $(CXX) $(CXXOPTS) -c $(CXXFLAGS) $(INCDIRS) -o $@ $<
+
+
+.PHONY : all clean objects realclean relink
+
+all : $(TARGET)
+
+relink : cleanso $(TARGET)
+
+objects : $(OBJECTS)
+
+$(TARGET) : $(OBJECTS)
+	-@ echo
+	  $(LD) $(LDFLAGS) -o $(TARGET) $(OBJECTS) $(LIBS)
+
+$(OBJECTS) : $(HEADERS)
+
+staticlib: $(MEMBERS)
+
+clean :
+	-rm -f $(OBJECTS)
+
+cleanso :
+	-rm -f $(TARGET)
+
+realclean : clean cleanso
+
+test: objects tasap2.o
+	-@ echo
+	  $(LD) -g -o /tmp/tasap2 $(OBJECTS) $(LIBS) tasap2.o
Index: tags/Release2.1.0b/src/Makefile.woody
===================================================================
--- tags/Release2.1.0b/src/Makefile.woody	(revision 1196)
+++ tags/Release2.1.0b/src/Makefile.woody	(revision 1196)
@@ -0,0 +1,196 @@
+TARGET   := /tmp/_asap.so
+
+# the casa environment AIPSPATH has to be defined
+CASAROOT  := $(word 1, $(AIPSPATH))
+CASAARCH  := $(word 2, $(AIPSPATH))
+CASAINC   := -I$(CASAROOT)/code/include -I$(CASAROOT)/code/casa
+CASALIB   := $(CASAROOT)/$(CASAARCH)/lib
+
+
+# the compiler
+CXX      := g++-3.2.3
+
+# compiler flags
+CXXFLAGS := -fPIC -O3
+CXXFLAGS += -ansi -pedantic -Wno-long-long -Wall
+CXXOPTS := -DAIPS_NO_TEMPLATE_SRC
+
+# darwin specific flags
+ifeq "$(CASAARCH)" "darwin"
+   CXXFLAGS := -O2 -DAIPS_DARWIN
+   CXXFLAGS += -ansi -pedantic -Wno-long-long -Wno-long-double
+endif
+
+
+# cfitsio
+CFITSIOROOT := /usr/local
+# If aips++ is build with LFS you need to have
+# libcfitsio with lfs support
+CFITSIOINC := -I$(CFITSIOROOT)/include
+CFITSIOLIB := $(CFITSIOROOT)/lib/libcfitsio_lfs.a
+#CFITSIOLIB := -L$(CFITSIOROOT) -lcfitsio
+
+#rpfits
+RPFITSROOT := /usr/local
+RPFITSLIB := $(RPFITSROOT)/lib/librpfits.a
+
+# This assumes all casa libs are static only (*.a)
+# if not than there might by symbol resolution errors.
+CASAPPLIB := $(CASALIB)/version.o \
+             -L$(CASALIB) \
+             -latnf -limages -lms -lcomponents -lcoordinates \
+             -llattices -lfits -lmeasures -lmeasures_f \
+             -ltables -lscimath -lscimath_f -lcasa  \
+             $(CASALIB)/libwcs.a \
+             $(RPFITSLIB) $(CFITSIOLIB) -lg2c -lstdc++
+
+# darwin specific CASA flags
+ifeq "$(CASAARCH)" "darwin"
+   CASAPPLIB += -framework vecLib
+else
+   CASAPPLIB += -llapack -lblas
+endif
+
+# the linker
+LD        := $(CXX)
+
+LDFLAGS   := -shared -Wl$(TARGET) -s
+ifeq "$(CASAARCH)" "darwin"
+   LDFLAGS   := -dynamiclib -single_module
+endif
+
+# python only 2.3 has been tested
+PYVERSION := 2.3
+PYTHONROOT := /usr/local
+PYTHONINC := -I$(PYTHONROOT)/include/python$(PYVERSION)
+PYTHONLIB := -L$(PYTHONROOT)/lib -lpython$(PYVERSION)
+
+# has to be build with same g++ version as casa
+BOOSTROOT := /usr/local
+BOOSTLIB  := -L$(BOOSTROOT)/lib -lboost_python$(PYVERSION)
+BOOSTINC  := -I$(BOOSTROOT)/include/boost
+
+
+# DO NOT MODIFY AFTER THIS
+INCDIRS   := -I. $(CASAINC)  $(CFITSIOINC) $(BOOSTINC) $(PYTHONINC)
+LIBS      := $(PYTHONLIB)  $(BOOSTLIB) $(CASAPPLIB)
+
+CCOBJECTS := MathUtils.o \
+             MathUtils2.o \
+             RowAccumulator.o \
+             Logger.o \
+             STAttr.o \
+             STHeader.o \
+             STFiller.o \
+             STMath.o \
+             STSubTable.o \
+             STMolecules.o \
+             STTcal.o \
+             STFocus.o \
+             STWeather.o \
+             STFrequencies.o \
+             STHistory.o \
+             STSelector.o \
+	     STLineFinder.o \
+             STFitter.o \
+             STFitEntry.o \
+             STFit.o \
+             STPol.o \
+             STPolLinear.o \
+             STPolStokes.o \
+             STWriter.o \
+             STAsciiWriter.o \
+             Scantable.o \
+             Templates.o 
+PYOBJECTS := python_Scantable.o \
+             python_STFiller.o \
+             python_STSelector.o \
+             python_STMath.o \
+             python_Fitter.o \
+             python_Logger.o \
+             python_STLineFinder.o \
+             python_STFitEntry.o \
+             python_STWriter.o \
+	     python_asap.o
+
+OBJECTS := $(CCOBJECTS) $(PYOBJECTS)
+
+HEADERS   := MathUtils.h \
+             Logger.h \
+             STAttr.h \
+             STHeader.h \
+             Scantable.h \
+             STFiller.h \
+             STMath.h \
+             RowAccumulator.h \
+             STSubTable.h \
+             STMolecules.h \
+             STTcal.h \
+             STFocus.h \
+             STWeather.h \
+             STHistory.h \
+             STFrequencies.h \
+             STDefs.h \
+             STWriter.h \
+             STSelector.h \
+	     STFitter.h \
+	     STFit.h \
+	     STFitEntry.h \
+	     STLineFinder.h \
+             Factory.h \
+             FactoryBase.h \
+	     STPol.h \
+	     STPolStokes.h \
+	     STPolLinear.h \
+	     STWriter.h \
+	     STAsciiWriter.h
+
+STATICCCLIB := libasap.a
+MEMBERS := $(patsubst %,$(STATICCCLIB)(%),$(CCOBJECTS))
+
+.SUFFIXES: .c .o .cpp .cc .cxx .C
+
+.cpp.o:
+	-@ echo
+	  $(CXX) $(CXXOPTS) -c $(CXXFLAGS) $(INCDIRS) -o $@ $<
+
+.cc.o:
+	-@ echo
+	  $(CXX) $(CXXOPTS) -c $(CXXFLAGS) $(INCDIRS) -o $@ $<
+
+.cxx.o:
+	-@ echo
+	  $(CXX) $(CXXOPTS) -c $(CXXFLAGS) $(INCDIRS) -o $@ $<
+
+.C.o:
+	-@ echo
+	  $(CXX) $(CXXOPTS) -c $(CXXFLAGS) $(INCDIRS) -o $@ $<
+
+
+.PHONY : all clean objects realclean relink
+
+all : $(TARGET)
+
+relink : cleanso $(TARGET)
+
+objects : $(OBJECTS)
+
+$(TARGET) : $(OBJECTS)
+	-@ echo
+	  $(LD) $(LDFLAGS) -o $(TARGET) $(OBJECTS) $(LIBS)
+
+$(OBJECTS) : $(HEADERS)
+
+staticlib: $(MEMBERS)
+
+clean :
+	-rm -f $(OBJECTS)
+
+cleanso :
+	-rm -f $(TARGET)
+
+realclean : clean cleanso
+
+test: objects tasap2.o
+	-@ echo
+	  $(LD) -g -o /tmp/tasap2 $(OBJECTS) $(LIBS) tasap2.o
Index: tags/Release2.1.0b/src/MathUtils.cpp
===================================================================
--- tags/Release2.1.0b/src/MathUtils.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/MathUtils.cpp	(revision 1196)
@@ -0,0 +1,101 @@
+//#---------------------------------------------------------------------------
+//# MathUtilities.cc: General math operations
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id:
+//#---------------------------------------------------------------------------
+
+#include <casa/aips.h>
+#include <casa/Arrays/Vector.h>
+#include <casa/Arrays/MaskedArray.h>
+#include <casa/Arrays/MaskArrMath.h>
+#include <casa/Arrays/VectorSTLIterator.h>
+#include <casa/BasicSL/String.h>
+
+#include "MathUtils.h"
+
+using namespace casa;
+
+float mathutil::statistics(const String& which,
+                           const MaskedArray<Float>& data)
+{
+   String str(which);
+   str.upcase();
+   if (str.contains(String("MIN"))) {
+      return min(data);
+   } else if (str.contains(String("MAX"))) {
+      return max(data);
+   } else if (str.contains(String("SUMSQ"))) {
+      return sumsquares(data);
+   } else if (str.contains(String("SUM"))) {
+      return sum(data);
+   } else if (str.contains(String("MEAN"))) {
+      return mean(data);
+   } else if (str.contains(String("VAR"))) {
+      return variance(data);
+   } else if (str.contains(String("STDDEV"))) {
+      return stddev(data);
+   } else if (str.contains(String("AVDEV"))) {
+      return avdev(data);
+   } else if (str.contains(String("RMS"))) {
+      uInt n = data.nelementsValid();
+      return sqrt(sumsquares(data)/n);
+   } else if (str.contains(String("MED"))) {
+      return median(data);
+   }
+   return 0.0;
+}
+
+
+void mathutil::replaceMaskByZero(Vector<Float>& data, const Vector<Bool>& mask)
+{
+   for (uInt i=0; i<data.nelements(); i++) {
+      if (!mask[i]) data[i] = 0.0;
+   }
+}
+
+
+std::vector<std::string> mathutil::tovectorstring(const Vector<String>& in)
+{
+  std::vector<std::string> out;
+  VectorSTLIterator<String> it(in);
+  for (uInt i=0; it != in.end(); ++it,++i) {
+    out.push_back(*it);
+  }
+  return out;
+}
+
+Vector<String> mathutil::toVectorString(const std::vector<std::string>& in)
+{
+  Vector<String> out(in.size());
+  uInt i=0;
+  std::vector<std::string>::const_iterator it;
+  for (it=in.begin();it != in.end();++it,++i) {
+    out[i] = casa::String(*it);
+  }
+  return out;
+}
Index: tags/Release2.1.0b/src/MathUtils.h
===================================================================
--- tags/Release2.1.0b/src/MathUtils.h	(revision 1196)
+++ tags/Release2.1.0b/src/MathUtils.h	(revision 1196)
@@ -0,0 +1,84 @@
+//#---------------------------------------------------------------------------
+//# MathUtilities.h: General math operations
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+#ifndef MATHUTILS_H
+#define MATHUTILS_H
+
+#include <string>
+#include <vector>
+#include <casa/aips.h>
+#include <casa/Arrays/Vector.h>
+#include <casa/BasicSL/String.h>
+
+namespace mathutil {
+
+// Hanning smoothing
+/**
+ * Hanning smooth a masked vector
+ * @param out the smoothed vector
+ * @param outmask  the smoothed mask
+ * @param in the input vector
+ * @param mask the input mask
+ * @param relaxed a weighting scheme
+ * @param ignoreOther drop every second channel (NYI)
+ */
+template <class T>
+void hanning(casa::Vector<T>& out, casa::Vector<casa::Bool>& outmask,
+             const casa::Vector<T>& in, const casa::Vector<casa::Bool>& mask,
+             casa::Bool relaxed=casa::False,
+             casa::Bool ignoreOther=casa::False);
+
+// Generate specified statistic
+float statistics(const casa::String& which,
+                 const casa::MaskedArray<casa::Float>& data);
+
+// Replace masked value by zero
+void replaceMaskByZero(casa::Vector<casa::Float>& data,
+                       const casa::Vector<casa::Bool>& mask);
+
+/**
+ * Convert a std::vector of std::string
+ * to a casa::Vector casa::String
+ * @param in
+ * @return
+ */
+std::vector<std::string> tovectorstring(const casa::Vector<casa::String>& in);
+
+/**
+ * Convert a casa::Vector of casa::String
+ * to a stl std::vector of stl std::string
+ * @param in
+ * @return
+ */
+casa::Vector<casa::String> toVectorString(const std::vector<std::string>& in);
+
+}
+
+#endif
Index: tags/Release2.1.0b/src/MathUtils2.cpp
===================================================================
--- tags/Release2.1.0b/src/MathUtils2.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/MathUtils2.cpp	(revision 1196)
@@ -0,0 +1,88 @@
+//#---------------------------------------------------------------------------
+//# MathUtilities.cc: General math operations
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id:$
+//#---------------------------------------------------------------------------
+
+#include <casa/aips.h>
+#include <casa/Arrays/Array.h>
+#include <casa/Arrays/ArrayIter.h>
+#include <casa/Arrays/Vector.h>
+#include <casa/Arrays/VectorSTLIterator.h>
+
+#include "MathUtils.h"
+
+using namespace casa;
+
+template <class T>
+void mathutil::hanning(Vector<T>& out, Vector<Bool>& outmask,
+                       const Vector<T>& in, const Vector<Bool>& mask,
+                       Bool relaxed, Bool ignoreOther) {
+  Vector< Vector<T> > weights(8);
+  Vector<Float> vals(3);
+  vals = 0.0;weights[0] = vals;// FFF
+  vals[0] = 1.0; vals[1] = 0.0; vals[2] = 0.0; weights[1] = vals;// TFF
+  vals[0] = 0.0; vals[1] = 1.0; vals[2] = 0.0; weights[2] = vals;// FTF
+  vals[0] = 1.0/3.0; vals[1] = 2.0/3.0; vals[2] = 0.0; weights[3] = vals;// TTF
+  vals[0] = 0.0; vals[1] = 0.0; vals[2] = 1.0;weights[4] = vals;// FFT
+  vals[0] = 0.5; vals[1] = 0.0; vals[2] = 0.5; weights[5] = vals;// TFT
+  vals[0] = 0.0; vals[1] = 2.0/3.0; vals[2] = 1.0/3.0; weights[6] = vals;// FTT
+  vals[0] = 0.25; vals[1] = 0.5; vals[2] = 0.25; weights[7] = vals;// TTT
+  // Chris' case
+  Vector<Bool> weighted(8);
+  if (relaxed) {
+    weighted = False;
+    weighted[7] = True;
+
+  } else {
+    weighted = True;
+    weighted[0] = False;
+  }
+
+  out.resize(in.nelements());
+  outmask.resize(mask.nelements());
+  // make special case for first and last
+  /// ...here
+  // loop from 1..n-2
+  out.resize(in.nelements());
+  out[0] = in[0];out[out.nelements()-1] = in[in.nelements()-1];
+  outmask.resize(mask.nelements());
+  outmask = False;
+  uInt m;Vector<T>* w;
+  for (uInt i=1; i < out.nelements()-1;++i) {
+    m = mask[i-1] + 2*mask[i] + 4*mask[i+1];
+    w = &(weights[m]);
+    if (weighted[m]) {
+      out[i] = (*w)[0]*in[i-1] + (*w)[1]*in[i] + (*w)[2]*in[i+1];
+      outmask[i] = True;
+    } else { // mask it
+      out[i] = in[i];//use arbitrary value
+      outmask[i] = False;
+    }
+  }
+}
Index: tags/Release2.1.0b/src/RowAccumulator.cpp
===================================================================
--- tags/Release2.1.0b/src/RowAccumulator.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/RowAccumulator.cpp	(revision 1196)
@@ -0,0 +1,144 @@
+//
+// C++ Implementation: RowAccumulator
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <Malte.Marquarding@csiro.au>, (C) 2005
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#include <casa/iomanip.h>
+
+#include <casa/Arrays/MaskArrMath.h>
+#include <casa/Arrays/ArrayMath.h>
+#include "RowAccumulator.h"
+
+
+using namespace casa;
+using namespace asap;
+
+RowAccumulator::RowAccumulator(WeightType wt) :
+  weightType_(wt),
+  initialized_(False)
+{
+  reset();
+}
+
+RowAccumulator::~RowAccumulator()
+{
+}
+
+
+void RowAccumulator::add( const Vector< Float >& v,
+                          const Vector< Bool >& m,
+                          const Vector< Float >& tsys,
+                          Double interval,
+                          Double time )
+{
+  if (!initialized_) {
+    Vector<Float> dummy(v.nelements(), 0.0);
+    spectrum_.setData(dummy, Vector<Bool>(m.nelements(), True));
+    n_.setData(Vector<Float>(v.nelements(), 0.0), m);
+    weightSum_.setData(Vector<Float>(v.nelements(), 0.0), m);
+    tsysSum_.resize(tsys.nelements()); tsysSum_=0.0;
+  }
+  // add spectrum related weights, so far it is variance only.
+  Float totalweight = 1.0;
+  totalweight *= addTsys(tsys);
+  totalweight *= addInterval(interval);
+  addTime(time);
+  addSpectrum(v, m, totalweight);
+  initialized_ = True;
+}
+
+void RowAccumulator::addSpectrum( const Vector< Float >& v,
+                                  const Vector< Bool >& m,
+                                  Float weight)
+{
+  Float totalweight = weight;
+  MaskedArray<Float> data(v,m);
+  if ( weightType_ == asap::VAR ) {
+    if (m.nelements() == userMask_.nelements()) {
+      Float fac = 1.0/variance(data(userMask_));
+      totalweight *= fac;
+    } else {
+      Float fac = 1.0/variance(data);
+      totalweight *= fac;
+    }
+  }
+  data *= totalweight;
+  MaskedArray<Float> wadd(Vector<Float>(m.nelements(),totalweight), m);
+  weightSum_ += wadd;
+  spectrum_ += data;
+  const MaskedArray<Float> inc(Vector<Float>(m.nelements(),1.0), m);
+  n_ += inc;
+}
+
+Float RowAccumulator::addTsys( const casa::Vector< casa::Float > & v )
+{
+  // @fixme this assume tsys is the same for all channels
+
+  Float w = 1.0;
+  tsysSum_ += v[0];
+  if ( weightType_ == asap::TSYS  || weightType_ == asap::TINTSYS ) {
+    w /= (v[0]*v[0]);
+  }
+  return w;
+}
+
+void asap::RowAccumulator::addTime( casa::Double t )
+{
+  timeSum_ += t;
+}
+
+Float asap::RowAccumulator::addInterval( casa::Double inter )
+{
+  Float w = 1.0;
+  intervalSum_ += inter;
+  if ( weightType_ == asap::TINT || weightType_ == asap::TINTSYS ) {
+    w /= Float(inter);
+  }
+  return w;
+}
+
+void asap::RowAccumulator::reset( )
+{
+  initialized_ = False;
+  intervalSum_ = 0.0;
+  tsysSum_.resize();
+  timeSum_ = 0.0;
+}
+
+casa::Vector< casa::Float > RowAccumulator::getSpectrum( ) const
+{
+  return (spectrum_/weightSum_).getArray();
+}
+
+casa::Double asap::RowAccumulator::getTime( ) const
+{
+  return timeSum_/max(n_);
+}
+
+casa::Double asap::RowAccumulator::getInterval( ) const
+{
+  return intervalSum_;
+}
+
+casa::Vector< casa::Bool > RowAccumulator::getMask( ) const
+{
+  return spectrum_.getMask();
+}
+
+casa::Vector< casa::Float > asap::RowAccumulator::getTsys( ) const
+{
+  // @fixme this assummes tsys.nelements() == 1
+  return tsysSum_/max(n_);
+}
+
+void asap::RowAccumulator::setUserMask( const casa::Vector< casa::Bool > & m )
+{
+  userMask_.resize();
+  userMask_ = m;
+}
Index: tags/Release2.1.0b/src/RowAccumulator.h
===================================================================
--- tags/Release2.1.0b/src/RowAccumulator.h	(revision 1196)
+++ tags/Release2.1.0b/src/RowAccumulator.h	(revision 1196)
@@ -0,0 +1,111 @@
+//
+// C++ Interface: RowAccumulator
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <Malte.Marquarding@csiro.au>, (C) 2005
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPROWACCUMULATOR_H
+#define ASAPROWACCUMULATOR_H
+
+#include <casa/aips.h>
+#include <casa/Arrays/Vector.h>
+#include <casa/Arrays/MaskedArray.h>
+#include "STDefs.h"
+
+namespace asap {
+/**
+  * This class accumulates spectra and weights and returns the averaged data
+  * @brief Class for averaging of spectra
+  * @author Malte Marquarding
+  * @date $Date:$
+  * @version
+  */
+class RowAccumulator {
+
+public:
+
+  /**
+   * Constructor taking a weight type as defined in @ref STDefs
+   */
+  RowAccumulator(WeightType wt = asap::NONE);
+
+ ~RowAccumulator();
+
+  /**
+    * add a new "row" to the accumulator
+    * @param v the spectrum
+    * @param m the mask for the spectrum
+    * @param tsys the Tsys corresponing to the spectrum
+    * @param interval the intergration time
+    * @param the time of the observation
+    */
+  void add(const casa::Vector<casa::Float>& v,
+           const casa::Vector<casa::Bool>& m,
+           const casa::Vector<casa::Float>& tsys,
+           casa::Double interval,
+           casa::Double time);
+  /**
+    * Also set a user mask which get combined with the individual masks
+    * from the spectra
+    * @param m a boolean mask of teh same length as the spectrum
+    */
+  void setUserMask(const casa::Vector<casa::Bool>& m);
+  /**
+    * Get the spectrum. Applies the normalisation (averaging)
+    * @return the spectrum vector
+    */
+  casa::Vector<casa::Float> getSpectrum() const;
+  /**
+    * Get the Tsys. Applies the normalisation (averaging)
+    * @return the Tsys vector
+    */
+  casa::Vector<casa::Float> getTsys() const;
+  /**
+    * Get the spectrum's mask. Applies the normalisation (averaging)
+    * @return the mask vector
+    */
+  casa::Vector<casa::Bool> getMask() const;
+  /**
+    * Get the total interval.
+    * @return the integration time
+    */
+  casa::Double getInterval() const;
+  /**
+    * Get the time of the observation. Retrieves the "mean" time.
+    * @return the integration time
+    */
+  casa::Double getTime() const;
+  /**
+    * Reset the acummulator to the state at construction.
+    */
+  void reset();
+
+private:
+  void addSpectrum( const casa::Vector<casa::Float>& v,
+                    const casa::Vector<casa::Bool>& m,
+                    casa::Float weight);
+
+  casa::Float addTsys(const casa::Vector<casa::Float>& v);
+  casa::Float addInterval(casa::Double inter);
+  void addTime(casa::Double t);
+
+  WeightType weightType_;
+  casa::Bool initialized_;
+  //these are Vectors
+  casa::MaskedArray<casa::Float> spectrum_;
+  casa::MaskedArray<casa::Float> n_, weightSum_;
+
+  casa::Vector<casa::Bool> userMask_;
+
+  casa::Vector<casa::Float> tsysSum_;
+  casa::Double timeSum_;
+  casa::Double intervalSum_;
+};
+
+}
+#endif
Index: tags/Release2.1.0b/src/SConscript
===================================================================
--- tags/Release2.1.0b/src/SConscript	(revision 1196)
+++ tags/Release2.1.0b/src/SConscript	(revision 1196)
@@ -0,0 +1,28 @@
+import os,glob,shutil
+
+# import root environment
+Import( "env")
+myenv = env.Copy()
+
+# gather cpp files
+cpps = env.SGlob("*.cpp")
+pycpps = env.SGlob("python_*.cpp")
+for pf in pycpps: cpps.remove(pf)
+
+casalibd = os.path.join(myenv['CASAROOT'],myenv['CASAARCH'],'lib')
+
+# location of libcasav.a
+myenv.Prepend( LIBPATH = ['#/build'] )
+myenv.Install('#build', os.path.join(casalibd, 'libwcs.a'))
+myenv['SHLIBPREFIX']=""   #gets rid of lib prefix
+
+# need to create library here to fool  scons linker
+casav = myenv.StaticLibrary(target = "casav", source = os.path.join(casalibd, 'version.o'))
+libasap =  myenv.StaticLibrary(target = "asap", source = cpps)
+myenv.Prepend( LIBS =  ['asap'] )
+
+# Finally create the library for the module
+so = myenv.SharedLibrary( target = "_asap", source = pycpps )
+#test = myenv.Program("test", cpps)
+Return("so")
+
Index: tags/Release2.1.0b/src/SDFITSImageWriter.cc
===================================================================
--- tags/Release2.1.0b/src/SDFITSImageWriter.cc	(revision 1196)
+++ tags/Release2.1.0b/src/SDFITSImageWriter.cc	(revision 1196)
@@ -0,0 +1,266 @@
+//#---------------------------------------------------------------------------
+//# SDFITSImageWriter.cc: ASAP class to write out single dish spectra as FITS images
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+
+#include "SDFITSImageWriter.h"
+
+#include <casa/aips.h>
+#include <casa/Arrays/Array.h>
+#include <casa/Arrays/Vector.h>
+#include <casa/Arrays/VectorIter.h>
+#include <casa/Utilities/CountedPtr.h>
+#include <casa/OS/Directory.h>
+
+#include <coordinates/Coordinates/CoordinateUtil.h>
+#include <coordinates/Coordinates/CoordinateSystem.h>
+#include <coordinates/Coordinates/SpectralCoordinate.h>
+#include <coordinates/Coordinates/DirectionCoordinate.h>
+#include <coordinates/Coordinates/StokesCoordinate.h>
+#include <coordinates/Coordinates/ObsInfo.h>
+
+#include <images/Images/ImageFITSConverter.h>
+#include <images/Images/TempImage.h>
+
+#include <lattices/Lattices/ArrayLattice.h>
+
+#include <measures/Measures/MEpoch.h>
+#include <measures/Measures/Stokes.h>
+
+#include <tables/Tables/Table.h>
+#include <tables/Tables/ScalarColumn.h>
+#include <tables/Tables/ArrayColumn.h>
+
+#include "STDefs.h"
+#include "STHeader.h"
+#include "SDMemTable.h"
+#include "SDPol.h"
+
+using namespace casa;
+using namespace asap;
+
+
+SDFITSImageWriter::SDFITSImageWriter()
+{;}
+
+SDFITSImageWriter::~SDFITSImageWriter()
+{;}
+
+
+Bool SDFITSImageWriter::write(const SDMemTable& sdTable, 
+                              const String& dirName, Bool toStokes,
+                              Bool verbose)
+{
+
+// Get global Header from Table
+
+   STHeader header = sdTable.getSTHeader();
+   MEpoch::Ref timeRef(sdTable.getTimeReference());
+   MDirection::Types dirRef = sdTable.getDirectionReference();
+
+// Prepare initial ObsInfo
+
+   ObsInfo oi;
+   oi.setTelescope(String(header.antennaname));
+   oi.setObserver(String(header.observer));
+
+// Column keywords
+
+   Table tab = sdTable.table();
+   ROArrayColumn<Double> dirCol(tab, String("DIRECTION"));
+   ROScalarColumn<Double> timeCol(tab, "TIME");
+   ROArrayColumn<uInt> freqidCol(tab, "FREQID");
+   ROArrayColumn<uInt> restfreqidCol(tab, "RESTFREQID");
+   ROScalarColumn<String> srcCol(tab, "SRCNAME");
+
+// Output Image Shape; spectral axis to be updated
+
+   IPosition shapeOut(4,1);                 // spectral, direction (2), stokes
+
+// Axes (should be in header)
+
+   const uInt beamAxis = asap::BeamAxis;
+   const uInt ifAxis = asap::IFAxis;
+   const uInt stokesAxis = asap::PolAxis;
+   const uInt chanAxis = asap::ChanAxis;
+   const Unit RAD(String("rad"));
+
+// Output Directory
+
+   String dirName2(dirName);
+   if (dirName.length()==0) {
+      dirName2 = String("asap_FITS");
+   }
+   Directory dir(dirName2);
+   dir.create(True);
+   ostringstream oss;
+   oss << "Created directory '" << dirName2 << "' for output files";
+
+   // Temps
+
+   Vector<Int> whichStokes(1,1);
+   Array<Double> whichDir;
+   Vector<Double> lonLat(2);
+   IPosition posDir(2,0);
+   Projection proj(Projection::SIN);                   // What should we use ?
+   Matrix<Double> xForm(2,2);
+   xForm = 0.0; xForm.diagonal() = 1.0;
+   Vector<Double> incLonLat(2,0.0);
+   Vector<Double> refPixLonLat(2,0.0);
+
+// Do we have linear or circular ?  No way to know yet...
+
+   Bool linear = True;
+
+// Loop over rows
+
+   uInt maxMem = 128;
+   Bool preferVelocity = False;
+   Bool opticalVelocity = False;
+   Int bitPix = -32;                     // FLoating point
+   Float minPix = 1.0;
+   Float maxPix = -1.0;
+   Bool overWrite = True;
+   Bool degLast = False;
+   Bool reallyVerbose = False;
+//
+   String errMsg;
+   const uInt nRows = sdTable.nRow();
+   for (uInt iRow=0; iRow<nRows; iRow++) {
+
+// Get data converted to Stokes
+
+      const MaskedArray<Float>& dataIn(sdTable.rowAsMaskedArray(iRow,toStokes));
+      const Array<Float>& values = dataIn.getArray();
+      const Array<Bool>& mask = dataIn.getMask();
+      const IPosition& shapeIn = dataIn.shape();
+      shapeOut(0) = shapeIn(chanAxis);
+      TiledShape tShapeOut(shapeOut);
+
+// Update ObsInfo (time changes per integration)
+
+      Double dTmp;
+      timeCol.get(iRow, dTmp);
+      MVEpoch tmp2(Quantum<Double>(dTmp, Unit(String("d"))));
+      MEpoch epoch(tmp2, timeRef);
+      oi.setObsDate(epoch);
+
+// Iterate through data in this row by spectra
+
+      ReadOnlyVectorIterator<Float> itData(values, chanAxis);
+      ReadOnlyVectorIterator<Bool> itMask(mask, chanAxis);
+      while (!itData.pastEnd()) {
+         const IPosition& pos = itData.pos();
+
+// Form SpectralCoordinate 
+
+         Vector<uInt> iTmp;
+         freqidCol.get(iRow, iTmp);
+         uInt freqID = iTmp(pos(ifAxis));
+//
+         restfreqidCol.get(iRow, iTmp);
+         uInt restFreqID = iTmp(pos(ifAxis));
+//
+         SpectralCoordinate sC = sdTable.getSpectralCoordinate(freqID, restFreqID, iRow);
+
+// Form DirectionCoordinate
+ 
+         dirCol.get(iRow, whichDir);
+         posDir(0) = pos(beamAxis);
+         posDir(1) = 0;
+         lonLat[0] = whichDir(posDir);
+//
+         posDir(0) = pos(beamAxis);
+         posDir(1) = 1;
+         lonLat[1] = whichDir(posDir);
+         DirectionCoordinate dC(dirRef, proj, lonLat[0], lonLat[1],
+                               incLonLat[0], incLonLat[1], xForm, 
+                               refPixLonLat[0], refPixLonLat[1]);
+
+// Form Stokes Coordinate (Stokes info still sketchy);
+
+         Stokes::StokesTypes stokes = SDPolUtil::convertStokes(pos(stokesAxis), toStokes, linear);
+         String stokesName = Stokes::name(stokes);
+         whichStokes(0) = Int(stokes);
+         StokesCoordinate stC(whichStokes);
+
+// Create CoordinateSystem
+
+         CoordinateSystem cSys;
+         cSys.addCoordinate(sC);
+         cSys.addCoordinate(dC);
+         cSys.addCoordinate(stC);
+         cSys.setObsInfo(oi);
+
+// Reform data into correct shape for output
+
+         Array<Float> t1(itData.array().reform(shapeOut));
+         Array<Bool> m1(itMask.array().reform(shapeOut));
+
+// Create aips++ Image
+
+         TempImage<Float> tIm(tShapeOut, cSys);
+         tIm.put(t1);
+//
+         ArrayLattice<Bool> latMask(m1);
+         tIm.attachMask(latMask);
+
+// Write out as FITS Image file
+
+         ostringstream oss2;
+         oss2 << "row" << iRow << "_beam" << pos(0) << "_if" 
+	     << pos(1) << "_" << stokesName  << "_" << srcCol(iRow) << ".fits";
+         String tS(oss2);
+         String fileName = dirName2 + String("/") + tS;
+         if (verbose) oss << endl << "Writing row " << iRow 
+			   << " into file '" << tS << "'";
+//
+         Bool ok = ImageFITSConverter::ImageToFITS(errMsg, tIm, fileName, 
+						   maxMem, preferVelocity,
+						   opticalVelocity, bitPix, 
+						   minPix, maxPix, overWrite,
+						   degLast, reallyVerbose);
+         if (!ok) {
+	   oss << endl<< "Error writing fits - " << errMsg;
+         }
+
+// Next spectrum
+
+         itData.next();
+         itMask.next();
+      }
+   }
+//
+   if (!verbose) {
+     oss << endl<< "Wrote " << nRows << " into individual FITS files";
+   }
+   pushLog(String(oss));
+   return True;
+}
+
Index: tags/Release2.1.0b/src/SDFITSImageWriter.h
===================================================================
--- tags/Release2.1.0b/src/SDFITSImageWriter.h	(revision 1196)
+++ tags/Release2.1.0b/src/SDFITSImageWriter.h	(revision 1196)
@@ -0,0 +1,60 @@
+//#---------------------------------------------------------------------------
+//# SDFITSImageWriter.h: ASAP class to write out single dish spectra as image FITS
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+#ifndef SDFITSIMAGEWRITER_H
+#define SDFITSIMAGEWRITER_H
+
+#include <casa/aips.h>
+#include <casa/BasicSL/String.h>
+#include <SDMemTable.h>
+
+#include "Logger.h"
+
+namespace asap {
+
+class SDFITSImageWriter : public Logger {
+public:
+  // Constructor
+  SDFITSImageWriter();
+  
+  // Destructor
+  ~SDFITSImageWriter();
+  
+  // Write out lots of FITS images, one per row, beam, IF and polarization
+  casa::Bool write(const SDMemTable& table, const casa::String& rootName,
+                   casa::Bool toStokes, casa::Bool verbose=casa::True);
+  
+private:
+  casa::Int convertStokes(casa::Int val, casa::Bool toStokes,
+			  casa::Bool linear) const;
+};
+
+}// namespace
+#endif
Index: tags/Release2.1.0b/src/STAsciiWriter.cpp
===================================================================
--- tags/Release2.1.0b/src/STAsciiWriter.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STAsciiWriter.cpp	(revision 1196)
@@ -0,0 +1,179 @@
+//#---------------------------------------------------------------------------
+//# STAsciiWriter.cc: ASAP class to write out single dish spectra as FITS images
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+
+#include <casa/aips.h>
+#include <casa/Arrays/Array.h>
+#include <casa/Arrays/Matrix.h>
+#include <casa/Arrays/Vector.h>
+#include <casa/Arrays/VectorIter.h>
+#include <casa/Utilities/CountedPtr.h>
+#include <casa/Quanta/Quantum.h>
+#include <casa/Quanta/MVAngle.h>
+#include <casa/Utilities/Assert.h>
+
+#include <casa/fstream.h>
+#include <casa/sstream.h>
+#include <casa/iomanip.h>
+
+#include <measures/Measures/MEpoch.h>
+
+#include <tables/Tables/Table.h>
+#include <tables/Tables/TableIter.h>
+#include <tables/Tables/TableRecord.h>
+#include <casa/Containers/RecordField.h>
+#include <tables/Tables/TableRow.h>
+#include <tables/Tables/ScalarColumn.h>
+#include <tables/Tables/ArrayColumn.h>
+
+#include "STDefs.h"
+#include "STHeader.h"
+#include "Scantable.h"
+#include "STAsciiWriter.h"
+
+using namespace casa;
+using namespace asap;
+
+
+STAsciiWriter::STAsciiWriter()
+{;}
+
+STAsciiWriter::~STAsciiWriter()
+{;}
+
+
+Bool STAsciiWriter::write(const Scantable& stable, const String& fileName)
+{
+
+// Get global Header from Table
+
+   STHeader hdr = stable.getHeader();
+
+// Column keywords
+
+   Table tab = stable.table();
+
+// Temps
+
+   const Unit RAD(String("rad"));
+
+// Open and write header file
+
+   String rootName(fileName);
+   if (rootName.length()==0) rootName = String("ascii");
+
+  Block<String> cols(4);
+  cols[0] = String("SCANNO");
+  cols[1] = String("CYCLENO");
+  cols[2] = String("BEAMNO");
+  cols[3] = String("IFNO");
+  TableIterator iter(tab, cols);
+  // Open data file
+  while ( !iter.pastEnd() ) {
+    Table t = iter.table();
+    ROTableRow row(t);
+    const TableRecord& rec = row.get(0);
+    String dirtype = stable.getDirectionRefString();
+    ostringstream onstr;
+    onstr << "SCAN" << rec.asuInt("SCANNO")
+    << "_CYCLE" << rec.asuInt("CYCLENO")
+    << "_BEAM" << rec.asuInt("BEAMNO")
+    << "_IF" << rec.asuInt("IFNO");
+    String fName = rootName + String(onstr) + String(".txt");
+    ofstream of(fName.chars(), ios::trunc);
+    int row0 = t.rowNumbers()[0];
+    MDirection mdir = stable.getDirection(row0);
+    of << setfill('#') << setw(70) << "" << setfill(' ') << endl;
+    addLine(of, "Name", rec.asString("SRCNAME"));
+    addLine(of, "Position", String(dirtype+ " "+formatDirection(mdir)));
+    addLine(of, "Time", stable.getTime(row0,true));
+    addLine(of, "Flux Unit", hdr.fluxunit);
+    addLine(of, "Pol Type", stable.getPolType());
+    addLine(of, "Abcissa", stable.getAbcissaLabel(row0));
+    addLine(of, "Beam No", rec.asuInt("BEAMNO"));
+    addLine(of, "IF No", rec.asuInt("IFNO"));
+    of << setfill('#') << setw(70) << "" << setfill(' ') << endl;
+
+    of << std::left << setw(16) << "x";
+    for ( unsigned int i=0; i<t.nrow(); ++i ) {
+      ostringstream os,os1;
+      os << "y" << i;
+      os1 << "yf" << i;
+      of << setw(16) << String(os);
+      of << setw(7) << String(os1);
+    }
+    of << endl;
+    std::vector<double> abc = stable.getAbcissa(row0);
+    ROArrayColumn<Float> specCol(t,"SPECTRA");
+    ROArrayColumn<uChar> flagCol(t,"FLAGTRA");
+    Matrix<Float> specs = specCol.getColumn();
+    Matrix<uChar> flags = flagCol.getColumn();
+    for ( unsigned int i=0; i<specs.nrow(); ++i ) {
+      of << setw(16) << setprecision(8) << abc[i] ;
+      for ( unsigned int j=0; j<specs.ncolumn(); ++j ) {
+        of << setw(16) << setprecision(8) << specs(i,j) ;
+        of << setw(7) << Int(flags(i,j));
+      }
+      of << endl;
+    }
+    of.close();
+    ostringstream oss;
+    oss << "Wrote " << fName;
+    pushLog(String(oss));
+    ++iter;
+  }
+  return True;
+}
+
+
+String STAsciiWriter::formatDirection(const MDirection& md) const
+{
+  Vector<Double> t = md.getAngle(Unit(String("rad"))).getValue();
+  Int prec = 7;
+
+  MVAngle mvLon(t[0]);
+  String sLon = mvLon.string(MVAngle::TIME,prec);
+  uInt tp = md.getRef().getType();
+  if (tp == MDirection::GALACTIC ||
+      tp == MDirection::SUPERGAL ) {
+    sLon = mvLon(0.0).string(MVAngle::ANGLE_CLEAN,prec);
+  }
+  MVAngle mvLat(t[1]);
+  String sLat = mvLat.string(MVAngle::ANGLE+MVAngle::DIG2,prec);
+  return sLon + String(" ") + sLat;
+}
+
+template <class T>
+void STAsciiWriter::addLine(ostream& of, const String& lbl, const T& value)
+{
+  String label = lbl+String(": ");
+  of << std::right << "# " << setw(15) << label << std::left
+     << setw(52) << value << setw(0) << "#"<< endl;
+}
Index: tags/Release2.1.0b/src/STAsciiWriter.h
===================================================================
--- tags/Release2.1.0b/src/STAsciiWriter.h	(revision 1196)
+++ tags/Release2.1.0b/src/STAsciiWriter.h	(revision 1196)
@@ -0,0 +1,69 @@
+//#---------------------------------------------------------------------------
+//# STAsciiWriter.h: ASAP class to write out single dish spectra as image FITS
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+#ifndef STASCIIWRITER_H
+#define STASCIIWRITER_H
+
+#include <casa/aips.h>
+#include <casa/BasicSL/String.h>
+#include <casa/ostream.h>
+
+#include "Scantable.h"
+#include "Logger.h"
+
+namespace casa {
+  template<class T> class Vector;
+  class MDirection;
+}
+
+namespace asap {
+/**
+ * A class to export a Scnatble to ASCII file(s)
+ */
+class STAsciiWriter : public Logger {
+public:
+// Constructor
+  STAsciiWriter();
+
+// Destructor
+  virtual ~STAsciiWriter();
+
+// Write out ascii table
+  casa::Bool write(const Scantable& table, const casa::String& name);
+
+private:
+  casa::String formatDirection(const casa::MDirection& md) const;
+  template <class T>
+  void addLine(std::ostream& of, const casa::String& lbl,
+               const T& value);
+};
+
+}// namespace
+#endif
Index: tags/Release2.1.0b/src/STAttr.cpp
===================================================================
--- tags/Release2.1.0b/src/STAttr.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STAttr.cpp	(revision 1196)
@@ -0,0 +1,346 @@
+//#---------------------------------------------------------------------------
+//# STAttr.cc: A collection of attributes for different telescopes
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id:
+//#---------------------------------------------------------------------------
+
+#include <casa/aips.h>
+#include <casa/Arrays/Vector.h>
+#include <casa/Arrays/ArrayMath.h>
+#include <casa/Exceptions.h>
+#include <casa/Quanta/QC.h>
+#include <casa/Quanta/Quantum.h>
+#include <casa/Quanta/MVTime.h>
+
+#include <measures/Measures/MEpoch.h>
+
+#include <scimath/Mathematics/InterpolateArray1D.h>
+
+#include "STAttr.h"
+
+using namespace casa;
+using namespace asap;
+
+STAttr::STAttr()
+{
+   initData();
+}
+
+STAttr::STAttr(const STAttr& other)
+{
+   initData();                     // state just private 'static' data
+}
+
+STAttr& STAttr::operator=(const STAttr& other)
+{
+  if (this != &other) {
+    ;                             // state just private 'static' data
+  }
+  return *this;
+}
+
+STAttr::~STAttr()
+{;}
+
+
+Float STAttr::diameter(Instrument inst)  const
+{
+   Float D = 1.0;
+   switch (inst) {
+      case ATMOPRA:
+        {
+           D = 22.0;
+        }
+        break;
+      case ATPKSMB:
+      case ATPKSHOH:
+        {
+           D = 64.0;
+        }
+        break;
+      case TIDBINBILLA:
+        {
+           D = 70.0;
+        }
+        break;
+      case CEDUNA:
+        {
+           D = 30.0;
+        }
+        break;
+      case HOBART:
+        {
+           D = 26.0;
+        }
+        break;
+      default:
+        {
+            throw(AipsError("Unknown instrument"));
+        }
+   }
+   return D;
+}
+
+Vector<Float> STAttr::beamEfficiency(Instrument inst, const MEpoch& dateObs,
+				      const Vector<Float>& freqs) const
+{
+
+  // Look at date where appropriate
+   MVTime t(dateObs.getValue());
+   uInt year = t.year();
+
+   Vector<Float> facs(freqs.nelements(),1.0);
+   switch (inst) {
+   case ATMOPRA:
+        {
+	  if (year<2003) {
+	    pushLog("There is no beam efficiency data from before 2003"
+		    " - using 2003 data");
+	    facs = interp(freqs/1.0e9f, MopEtaBeamX_, MopEtaBeam2003Y_);
+	  } else if (year==2003) {
+	    pushLog("Using beam efficiency data from 2003");
+	    facs = interp(freqs/1.0e9f, MopEtaBeamX_, MopEtaBeam2003Y_);
+	  } else {
+	    pushLog("Using beam efficiency data from 2004");
+	    facs = interp(freqs/1.0e9f, MopEtaBeamX_, MopEtaBeam2004Y_);
+	  }
+        }
+        break;
+   default:
+     {
+       pushLog("No beam efficiency data for this instrument - assuming unity");
+     }
+   }
+   return facs;
+}
+
+Vector<Float> STAttr::apertureEfficiency(Instrument inst,
+					 const MEpoch& dateObs,
+					 const Vector<Float>& freqs) const
+{
+
+  // Look at date where appropriate
+  MVTime t(dateObs.getValue());
+  uInt year = t.year();
+
+  Vector<Float> facs(freqs.nelements(),1.0);
+  switch (inst) {
+  case ATMOPRA:
+    {
+      if (year<2004) {
+	pushLog("There is no aperture efficiency data from before 2004"
+		" - using 2004 data");
+	facs = interp(freqs/1.0e9f, MopEtaApX_, MopEtaAp2004Y_);
+      } else {
+	pushLog("Using aperture efficiency data from 2004");
+	facs = interp(freqs/1.0e9f, MopEtaApX_, MopEtaAp2004Y_);
+      }
+    }
+    break;
+  case TIDBINBILLA:
+    {
+      facs = interp(freqs/1.0e9f, TidEtaApX_, TidEtaApY_);
+    }
+    break;
+  default:
+    {
+      pushLog("No aperture efficiency data for this instrument"
+	      " - assuming unity");
+    }
+  }
+  return facs;
+}
+
+Vector<Float> STAttr::JyPerK(Instrument inst, const MEpoch& dateObs,
+			     const Vector<Float>& freqs) const
+{
+
+  // Find what we need
+  Vector<Float> etaAp = apertureEfficiency(inst, dateObs, freqs);
+  Float D = diameter(inst);
+  // Compute it
+   Vector<Float> facs(freqs.nelements(),1.0);
+   for (uInt i=0; i<freqs.nelements(); i++) {
+     facs(i) = STAttr::findJyPerK(etaAp(i), D);
+   }
+   return facs;
+}
+
+
+Float STAttr::findJyPerK(Float etaAp, Float D)
+  //
+  // Converts K -> Jy
+  // D in m
+  //
+{
+  Double kb = QC::k.getValue(Unit(String("erg/K")));
+  Float gA = C::pi * D * D / 4.0;
+  return (2.0 * 1.0e19 * kb / etaAp / gA);
+}
+
+
+Vector<Float> STAttr::gainElevationPoly(Instrument inst) const
+{
+
+  // Look at date where appropriate
+  switch (inst) {
+  case TIDBINBILLA:
+    {
+      return TidGainElPoly_.copy();
+    }
+    break;
+  default:
+    {
+      Vector<Float> t;
+      return t.copy();
+    }
+  }
+}
+
+std::string STAttr::feedPolType(Instrument inst) const
+{
+  std::string type;
+  switch (inst) {
+  case ATMOPRA:
+  case ATPKSMB:
+  case ATPKSHOH:
+    {
+      type = "linear";
+    }
+    break;
+  case TIDBINBILLA:
+    {
+      type = "circular";
+    }
+    break;
+  default:
+    {
+      type = "linear";
+    }
+  }
+  return type;
+}
+
+
+// Private
+Vector<Float> STAttr::interp(const Vector<Float>& xOut,
+			     const Vector<Float>& xIn,
+			     const Vector<Float>& yIn) const
+{
+  Int method = 1;  // Linear
+  Vector<Float> yOut;
+  Vector<Bool> mOut;
+
+  Vector<Bool> mIn(xIn.nelements(),True);
+
+  InterpolateArray1D<Float,Float>::interpolate(yOut, mOut, xOut,
+					       xIn, yIn, mIn,
+					       method, True, True);
+
+  return yOut;
+}
+
+void STAttr::initData()
+  //
+  // Mopra data from Mopra web page
+  // Tid  data from Tid web page
+  // X in GHz
+  //
+{
+
+  // Beam efficiency
+   MopEtaBeamX_.resize(3);
+   MopEtaBeamX_(0) = 86.0;
+   MopEtaBeamX_(1) = 100.0;
+   MopEtaBeamX_(2) = 115.0;
+
+   MopEtaBeam2003Y_.resize(3);
+   MopEtaBeam2003Y_(0) = 0.39;
+   MopEtaBeam2003Y_(1) = 0.37;
+   MopEtaBeam2003Y_(2) = 0.37;          // replicated from (1)
+
+   MopEtaBeam2004Y_.resize(3);
+   MopEtaBeam2004Y_(0) = 0.49;
+   MopEtaBeam2004Y_(1) = 0.44;
+   MopEtaBeam2004Y_(2) = 0.42;
+
+   // Aperture efficiency
+   MopEtaApX_.resize(2);
+   MopEtaApX_(0) = 86.0;
+   MopEtaApX_(1) = 115.0;
+
+   MopEtaAp2004Y_.resize(2);
+   MopEtaAp2004Y_(0) = 0.33;
+   MopEtaAp2004Y_(1) = 0.24;
+
+   TidEtaApX_.resize(2);
+   TidEtaApY_.resize(2);
+   TidEtaApX_(0) = 18.0;
+   TidEtaApX_(1) = 26.5;
+   TidEtaApY_(0) = 0.4848;
+   TidEtaApY_(1) = 0.4848;
+
+   // Gain elevation correction polynomial coefficients (for elevation
+   // in degrees)
+
+   TidGainElPoly_.resize(3);
+   TidGainElPoly_(0) = 3.58788e-1;
+   TidGainElPoly_(1) = 2.87243e-2;
+   TidGainElPoly_(2) = -3.219093e-4;
+}
+
+
+Instrument STAttr::convertInstrument(const String& instrument,
+                                     Bool throwIt)
+{
+  String t(instrument);
+  t.upcase();
+
+  // The strings are what STReader returns, after cunning
+  // interrogation of station names... :-(
+  Instrument inst = asap::UNKNOWNINST;
+  if (t==String("DSS-43")) {
+    inst = TIDBINBILLA;
+  } else if (t==String("ATPKSMB")) {
+    inst = ATPKSMB;
+  } else if (t==String("ATPKSHOH")) {
+    inst = ATPKSHOH;
+  } else if (t==String("ATMOPRA")) {
+    inst = ATMOPRA;
+  } else if (t==String("CEDUNA")) {
+    inst = CEDUNA;
+  } else if (t==String("HOBART")) {
+    inst = HOBART;
+  } else {
+    if (throwIt) {
+      throw AipsError("Unrecognized instrument"
+		      " - use function scan.set_instrument to set");
+    }
+  }
+  return inst;
+}
Index: tags/Release2.1.0b/src/STAttr.h
===================================================================
--- tags/Release2.1.0b/src/STAttr.h	(revision 1196)
+++ tags/Release2.1.0b/src/STAttr.h	(revision 1196)
@@ -0,0 +1,130 @@
+//#---------------------------------------------------------------------------
+//# STAttr.h: Return known attributes about telescopes
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+#ifndef STATTR_H
+#define STATTR_H
+
+#include <string>
+
+#include <casa/aips.h>
+#include <measures/Measures/MEpoch.h>
+#include <casa/Utilities/CountedPtr.h>
+
+#include "Logger.h"
+#include "STDefs.h"
+
+namespace casa {
+  template<class T> class Vector;
+}
+
+namespace asap {
+
+class STAttr : public Logger {
+
+ public:
+
+  // Constructor
+  STAttr();
+
+  // Destructor
+  virtual ~STAttr();
+
+  // Copy Constructor (copy semantics)
+  STAttr(const STAttr& other);
+
+  // Assignment  (copy semantics)
+  STAttr &operator=(const STAttr& other);
+
+  // Telescope diameter(m). Throws exception if unknown.
+  casa::Float diameter(Instrument inst) const;
+
+  // Beam efficiency.  Frequency in Hz.  Returns 1 if unknown.
+  casa::Vector<casa::Float>
+  beamEfficiency(Instrument instr, const casa::MEpoch& dateObs,
+                 const casa::Vector<casa::Float>& freqs) const;
+
+  // Aperture efficiency. Frequency in Hz.  Returns 1 if unknown.
+  casa::Vector<casa::Float>
+  apertureEfficiency(Instrument instr,
+                     const casa::MEpoch& dateObs,
+                     const casa::Vector<casa::Float>& freqs) const;
+
+  // Find factor to convert Jy -> K for this telescope, date of
+  // observation and frequency (Hz)
+  casa::Vector<casa::Float> JyPerK(Instrument instr,
+                                   const casa::MEpoch& dateObs,
+                                   const casa::Vector<casa::Float>& freqs)
+    const;
+
+  // Gain Elevation polynomial correction coefficients (elevation in
+  // degrees) Returns length 0 if not known.
+   casa::Vector<casa::Float> gainElevationPoly(Instrument instr) const;
+
+  // Find feed polarization type of feeds. In future this needs to come
+  // from the data themselves
+  std::string feedPolType(Instrument) const;
+
+  // Helper function to check instrument (antenna) name and give enum
+  static Instrument convertInstrument(const casa::String& instrument,
+                                      casa::Bool throwIt);
+
+  // Helper function.  Finds factor to convert K -> Jy. Provide
+  // aperture efficiency and dish geometric diameter (m)
+  static casa::Float findJyPerK(casa::Float etaAp, casa::Float D);
+
+private:
+
+  // Static data
+  casa::Vector<casa::Float> MopEtaBeamX_;     // Beam efficiency
+  casa::Vector<casa::Float> MopEtaBeam2003Y_;
+  casa::Vector<casa::Float> MopEtaBeam2004Y_;
+
+  casa::Vector<casa::Float> MopEtaApX_;       // Aperture efficiency
+  casa::Vector<casa::Float> MopEtaAp2004Y_;
+
+  casa::Vector<casa::Float> TidEtaApX_;       // Aperture efficiency
+  casa::Vector<casa::Float> TidEtaApY_;
+
+  casa::Vector<casa::Float> TidGainElPoly_;   // Gain-el correction poly coeffs
+
+  // Init private data
+  void initData();
+
+  // Linear interpolation
+  casa::Vector<casa::Float> interp(const casa::Vector<casa::Float>& xOut,
+                                   const casa::Vector<casa::Float>& xIn,
+                                   const casa::Vector<casa::Float>& yIn) const;
+
+
+};
+
+} // namespace
+
+#endif
Index: tags/Release2.1.0b/src/STDefs.h
===================================================================
--- tags/Release2.1.0b/src/STDefs.h	(revision 1196)
+++ tags/Release2.1.0b/src/STDefs.h	(revision 1196)
@@ -0,0 +1,63 @@
+//#---------------------------------------------------------------------------
+//# STDefs.h: Global definitions
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+
+#ifndef ASAPDEFS_H
+#define ASAPDEFS_H
+
+namespace asap {
+  enum AxisNo { BeamAxis=0,
+                IFAxis,
+                PolAxis,
+                ChanAxis,
+                nAxes};
+
+  enum Instrument {UNKNOWNINST=0,
+                   ATPKSMB,
+                   ATPKSHOH,
+                   ATMOPRA,
+                   TIDBINBILLA,
+                   CEDUNA,
+                   HOBART,
+                   N_INSTRUMENTS};
+
+  enum FeedPolType {UNKNOWNFEED, LINEAR, CIRCULAR, N_POL};
+
+  enum WeightType {NONE=0, VAR, TSYS, TINT, TINTSYS, N_WEIGHTTYPES};
+
+  enum TableType {MEMORY=0, PERSISTENT};
+
+
+
+  const std::string SEPERATOR =
+   "--------------------------------------------------------------------------------";
+}
+
+#endif
Index: tags/Release2.1.0b/src/STFiller.cpp
===================================================================
--- tags/Release2.1.0b/src/STFiller.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STFiller.cpp	(revision 1196)
@@ -0,0 +1,350 @@
+//
+// C++ Implementation: STFiller
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#include <casa/iostream.h>
+#include <casa/iomanip.h>
+
+#include <casa/Exceptions.h>
+#include <casa/OS/Path.h>
+#include <casa/OS/File.h>
+#include <casa/Quanta/Unit.h>
+#include <casa/Arrays/ArrayMath.h>
+#include <casa/Arrays/ArrayLogical.h>
+#include <casa/Utilities/Regex.h>
+
+#include <casa/Containers/RecordField.h>
+
+#include <tables/Tables/TableRow.h>
+
+#include <atnf/PKSIO/PKSreader.h>
+
+#include "STDefs.h"
+#include "STAttr.h"
+
+#include "STFiller.h"
+#include "STHeader.h"
+
+using namespace casa;
+
+namespace asap {
+
+STFiller::STFiller() :
+  reader_(0),
+  header_(0),
+  table_(0)
+{
+}
+
+STFiller::STFiller( CountedPtr< Scantable > stbl ) :
+  reader_(0),
+  header_(0),
+  table_(stbl)
+{
+}
+
+STFiller::STFiller(const std::string& filename, int whichIF, int whichBeam ) :
+  reader_(0),
+  header_(0),
+  table_(0)
+{
+  open(filename, whichIF, whichBeam);
+}
+
+STFiller::~STFiller()
+{
+  close();
+}
+
+void STFiller::open( const std::string& filename, int whichIF, int whichBeam )
+{
+  if (table_.null())  {
+    table_ = new Scantable();
+  }
+  if (reader_)  { delete reader_; reader_ = 0; }
+  Bool haveBase, haveSpectra;
+
+  String inName(filename);
+  Path path(inName);
+  inName = path.expandedName();
+
+  File file(inName);
+  if ( !file.exists() ) {
+     throw(AipsError("File does not exist"));
+  }
+  filename_ = inName;
+
+  // Create reader and fill in values for arguments
+  String format;
+  Vector<Bool> beams, ifs;
+  Vector<uInt> nchans,npols;
+  if ( (reader_ = getPKSreader(inName, 0, 0, format, beams, ifs,
+                              nchans, npols, haveXPol_,haveBase, haveSpectra
+                              )) == 0 )  {
+    throw(AipsError("Creation of PKSreader failed"));
+  }
+  if (!haveSpectra) {
+    delete reader_;
+    reader_ = 0;
+    throw(AipsError("No spectral data in file."));
+    return;
+  }
+  nBeam_ = beams.nelements();
+  nIF_ = ifs.nelements();
+  // Get basic parameters.
+  if ( anyEQ(haveXPol_, True) ) {
+    pushLog("Cross polarization present");
+    for (uInt i=0; i< npols.nelements();++i) {
+      if (npols[i] < 3) npols[i] += 2;// Convert Complex -> 2 Floats
+    }
+  }
+  if (header_) delete header_;
+  header_ = new STHeader();
+  header_->nchan = max(nchans);
+  header_->npol = max(npols);
+  header_->nbeam = nBeam_;
+
+  Int status = reader_->getHeader(header_->observer, header_->project,
+                                  header_->antennaname, header_->antennaposition,
+                                  header_->obstype,header_->equinox,
+                                  header_->freqref,
+                                  header_->utc, header_->reffreq,
+                                  header_->bandwidth);
+
+  if (status) {
+    delete reader_;
+    reader_ = 0;
+    delete header_;
+    header_ = 0;
+    throw(AipsError("Failed to get header."));
+  }
+  if ((header_->obstype).matches("*SW*")) {
+    // need robust way here - probably read ahead of next timestamp
+    pushLog("Header indicates frequency switched observation.\n"
+               "setting # of IFs = 1 ");
+    nIF_ = 1;
+    header_->obstype = String("fswitch");
+  }
+  // Determine Telescope and set brightness unit
+
+  Bool throwIt = False;
+  Instrument inst = STAttr::convertInstrument(header_->antennaname, throwIt);
+  header_->fluxunit = "Jy";
+  if (inst==ATMOPRA || inst==TIDBINBILLA) {
+     header_->fluxunit = "K";
+  }
+  STAttr stattr;
+  header_->poltype = stattr.feedPolType(inst);
+  header_->nif = nIF_;
+  header_->epoch = "UTC";
+  // *** header_->frequnit = "Hz"
+  // Apply selection criteria.
+  Vector<Int> ref;
+  ifOffset_ = 0;
+  if (whichIF>=0) {
+    if (whichIF>=0 && whichIF<nIF_) {
+      ifs = False;
+      ifs(whichIF) = True;
+      header_->nif = 1;
+      nIF_ = 1;
+      ifOffset_ = whichIF;
+    } else {
+      delete reader_;
+      reader_ = 0;
+      delete header_;
+      header_ = 0;
+      throw(AipsError("Illegal IF selection"));
+    }
+  }
+  beamOffset_ = 0;
+  if (whichBeam>=0) {
+    if (whichBeam>=0 && whichBeam<nBeam_) {
+      beams = False;
+      beams(whichBeam) = True;
+      header_->nbeam = 1;
+      nBeam_ = 1;
+      beamOffset_ = whichBeam;
+    } else {
+      delete reader_;
+      reader_ = 0;
+      delete header_;
+      header_ = 0;
+      throw(AipsError("Illegal Beam selection"));
+    }
+  }
+  Vector<Int> start(nIF_, 1);
+  Vector<Int> end(nIF_, 0);
+  reader_->select(beams, ifs, start, end, ref, True, haveXPol_[0], False);
+  table_->setHeader(*header_);
+}
+
+void STFiller::close( )
+{
+  delete reader_;reader_=0;
+  delete header_;header_=0;
+  table_ = 0;
+}
+
+int asap::STFiller::read( )
+{
+  int status = 0;
+
+  Int    beamNo, IFno, refBeam, scanNo, cycleNo;
+  Float  azimuth, elevation, focusAxi, focusRot, focusTan,
+    humidity, parAngle, pressure, temperature, windAz, windSpeed;
+  Double bandwidth, freqInc, interval, mjd, refFreq, restFreq, srcVel;
+  String          fieldName, srcName, tcalTime, obsType;
+  Vector<Float>   calFctr, sigma, tcal, tsys;
+  Matrix<Float>   baseLin, baseSub;
+  Vector<Double>  direction(2), scanRate(2), srcDir(2), srcPM(2);
+  Matrix<Float>   spectra;
+  Matrix<uChar>   flagtra;
+  Complex         xCalFctr;
+  Vector<Complex> xPol;
+  while ( status == 0 ) {
+    status = reader_->read(scanNo, cycleNo, mjd, interval, fieldName,
+                          srcName, srcDir, srcPM, srcVel, obsType, IFno,
+                          refFreq, bandwidth, freqInc, restFreq, tcal, tcalTime,
+                          azimuth, elevation, parAngle, focusAxi,
+                          focusTan, focusRot, temperature, pressure,
+                          humidity, windSpeed, windAz, refBeam,
+                          beamNo, direction, scanRate,
+                          tsys, sigma, calFctr, baseLin, baseSub,
+                          spectra, flagtra, xCalFctr, xPol);
+    if ( status != 0 ) break;
+    Regex filterrx(".*[SL|PA]$");
+    Regex obsrx("^AT.+");
+    if ( header_->antennaname.matches(obsrx) && obsType.matches(filterrx)) {
+        //cerr << "ignoring paddle scan" << endl;
+        continue;
+    }
+    TableRow row(table_->table());
+    TableRecord& rec = row.record();
+    // fields that don't get used and are just passed through asap
+    RecordFieldPtr<Array<Double> > srateCol(rec, "SCANRATE");
+    *srateCol = scanRate;
+    RecordFieldPtr<Array<Double> > spmCol(rec, "SRCPROPERMOTION");
+    *spmCol = srcPM;
+    RecordFieldPtr<Array<Double> > sdirCol(rec, "SRCDIRECTION");
+    *sdirCol = srcDir;
+    RecordFieldPtr<Double> svelCol(rec, "SRCVELOCITY");
+    *svelCol = srcVel;
+    // the real stuff
+    RecordFieldPtr<Int> fitCol(rec, "FIT_ID");
+    *fitCol = -1;
+    RecordFieldPtr<uInt> scanoCol(rec, "SCANNO");
+    *scanoCol = scanNo-1;
+    RecordFieldPtr<uInt> cyclenoCol(rec, "CYCLENO");
+    *cyclenoCol = cycleNo-1;
+    RecordFieldPtr<Double> mjdCol(rec, "TIME");
+    *mjdCol = mjd;
+    RecordFieldPtr<Double> intCol(rec, "INTERVAL");
+    *intCol = interval;
+    RecordFieldPtr<String> srcnCol(rec, "SRCNAME");
+    RecordFieldPtr<Int> srctCol(rec, "SRCTYPE");
+    // try to auto-identify if it is on or off.
+    Regex rx(".*[e|w|_R]$");
+    Regex rx2("_S$");
+    Int match = srcName.matches(rx);
+    if (match) {
+      *srcnCol = srcName;
+    } else {
+      *srcnCol = srcName.before(rx2);
+    }
+    //*srcnCol = srcName;//.before(rx2);
+    *srctCol = match;
+    RecordFieldPtr<uInt> beamCol(rec, "BEAMNO");
+    *beamCol = beamNo-beamOffset_-1;
+    RecordFieldPtr<Int> rbCol(rec, "REFBEAMNO");
+    Int rb = -1;
+    if (nBeam_ > 1 ) rb = refBeam-1;
+    *rbCol = rb;
+    RecordFieldPtr<uInt> ifCol(rec, "IFNO");
+    *ifCol = IFno-ifOffset_- 1;
+    uInt id;
+    /// @todo this has to change when nchan isn't global anymore
+    id = table_->frequencies().addEntry(Double(header_->nchan/2),
+                                            refFreq, freqInc);
+    RecordFieldPtr<uInt> mfreqidCol(rec, "FREQ_ID");
+    *mfreqidCol = id;
+
+    id = table_->molecules().addEntry(restFreq);
+    RecordFieldPtr<uInt> molidCol(rec, "MOLECULE_ID");
+    *molidCol = id;
+
+    id = table_->tcal().addEntry(tcalTime, tcal);
+    RecordFieldPtr<uInt> mcalidCol(rec, "TCAL_ID");
+    *mcalidCol = id;
+    id = table_->weather().addEntry(temperature, pressure, humidity,
+                                    windSpeed, windAz);
+    RecordFieldPtr<uInt> mweatheridCol(rec, "WEATHER_ID");
+    *mweatheridCol = id;
+    RecordFieldPtr<uInt> mfocusidCol(rec, "FOCUS_ID");
+    id = table_->focus().addEntry(focusAxi, focusTan, focusRot);
+    *mfocusidCol = id;
+    RecordFieldPtr<Array<Double> > dirCol(rec, "DIRECTION");
+    *dirCol = direction;
+    RecordFieldPtr<Float> azCol(rec, "AZIMUTH");
+    *azCol = azimuth;
+    RecordFieldPtr<Float> elCol(rec, "ELEVATION");
+    *elCol = elevation;
+
+    RecordFieldPtr<Float> parCol(rec, "PARANGLE");
+    *parCol = parAngle;
+
+    RecordFieldPtr< Array<Float> > specCol(rec, "SPECTRA");
+    RecordFieldPtr< Array<uChar> > flagCol(rec, "FLAGTRA");
+    RecordFieldPtr< uInt > polnoCol(rec, "POLNO");
+
+    RecordFieldPtr< Array<Float> > tsysCol(rec, "TSYS");
+    // Turn the (nchan,npol) matrix and possible complex xPol vector
+    // into 2-4 rows in the scantable
+    Vector<Float> tsysvec(1);
+    // Why is spectra.ncolumn() == 3 for haveXPol_ == True
+    uInt npol = (spectra.ncolumn()==1 ? 1: 2);
+    for ( uInt i=0; i< npol; ++i ) {
+      tsysvec = tsys(i);
+      *tsysCol = tsysvec;
+      *polnoCol = i;
+
+      *specCol = spectra.column(i);
+      *flagCol = flagtra.column(i);
+      table_->table().addRow();
+      row.put(table_->table().nrow()-1, rec);
+    }
+    if ( haveXPol_[0] ) {
+      // no tsys given for xpol, so emulate it
+      tsysvec = sqrt(tsys[0]*tsys[1]);
+      *tsysCol = tsysvec;
+      // add real part of cross pol
+      *polnoCol = 2;
+      Vector<Float> r(real(xPol));
+      *specCol = r;
+      // make up flags from linears
+      /// @fixme this has to be a bitwise or of both pols
+      *flagCol = flagtra.column(0);// | flagtra.column(1);
+      table_->table().addRow();
+      row.put(table_->table().nrow()-1, rec);
+      // ad imaginary part of cross pol
+      *polnoCol = 3;
+      Vector<Float> im(imag(xPol));
+      *specCol = im;
+      table_->table().addRow();
+      row.put(table_->table().nrow()-1, rec);
+    }
+  }
+  if (status > 0) {
+    close();
+    throw(AipsError("Reading error occured, data possibly corrupted."));
+  }
+  return status;
+}
+
+}//namespace asap
Index: tags/Release2.1.0b/src/STFiller.h
===================================================================
--- tags/Release2.1.0b/src/STFiller.h	(revision 1196)
+++ tags/Release2.1.0b/src/STFiller.h	(revision 1196)
@@ -0,0 +1,108 @@
+//
+// C++ Interface: STFiller
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef STFILLER_H
+#define STFILLER_H
+
+#include <vector>
+#include <string>
+
+#include <casa/aips.h>
+#include <casa/iostream.h>
+#include <casa/Utilities/CountedPtr.h>
+#include <casa/BasicSL/String.h>
+#include <casa/Arrays/Vector.h>
+
+#include "Scantable.h"
+#include "STHeader.h"
+#include "Logger.h"
+
+class PKSreader;
+
+namespace asap {
+
+/**
+This class fills a Scantable from external data formats using the PKSReader class.
+
+@brief    A filler object for data import into Scantable
+@author   Malte Marquarding
+@date     2006/01/16
+@version  2.0a
+*/
+class STFiller : public Logger {
+public:
+
+  /**
+   * Default constructor
+   */
+  STFiller();
+
+
+  /**
+   * Constructor taking an existing Scantable to fill
+   * @param stbl
+   */
+  STFiller(casa::CountedPtr< Scantable > stbl);
+
+
+  /**
+    * A constructor for a filler with associated input file
+    * @param filename the input file (rpf,sdfite or ms)
+    * @param whichIF read a specific IF only (default -1 means all IFs)
+    * @param whichBeam read a specific beam only (default -1 means all beams)
+    */
+  STFiller( const std::string& filename, int whichIF=-1,
+                  int whichBeam=-1 );
+
+  /**
+   * Destructor
+   */
+  virtual ~STFiller();
+
+  /**
+   * associate the Filler with a file on disk
+   * @param filename the input file (rpf,sdfite or ms)
+   * @param whichIF read a specific IF only (default -1 means all IFs)
+   * @param whichBeam read a specific beam only (default -1 means all beams)
+   * @exception AipsError Creation of PKSreader failed
+   */
+  void open( const std::string& filename, int whichIF=-1, int whichBeam=-1 );
+
+  /**
+   * detach from file and clean up pointers
+   */
+  void close( );
+
+  /**
+   * Read in "rows" from the source file attached with open()
+   * @return a status flag passed on by PKSreader
+   *
+   * @li @c 0: ok
+   * @li >0: failed
+   */
+  int read( );
+
+  casa::CountedPtr<Scantable> getTable() const { return table_;}
+
+private:
+
+  PKSreader* reader_;
+  STHeader* header_;
+  casa::String filename_;
+  casa::CountedPtr< Scantable > table_;
+  casa::Int nIF_, nBeam_, nPol_, nChan_;
+  casa::uInt ifOffset_, beamOffset_;
+  casa::Vector<casa::Bool> haveXPol_;
+};
+
+} // namespace
+
+#endif
Index: tags/Release2.1.0b/src/STFillerWrapper.h
===================================================================
--- tags/Release2.1.0b/src/STFillerWrapper.h	(revision 1196)
+++ tags/Release2.1.0b/src/STFillerWrapper.h	(revision 1196)
@@ -0,0 +1,54 @@
+//#---------------------------------------------------------------------------
+//# STFillerWrapper.h: Wrapper classes to use CountedPtr
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id:$
+//#---------------------------------------------------------------------------
+#ifndef STFILLERWRAPPER_H
+#define STFILLERWRAPPER_H
+
+#include <string>
+
+#include "ScantableWrapper.h"
+#include "STFiller.h"
+
+namespace asap {
+
+class STFillerWrapper : public STFiller {
+public:
+  STFillerWrapper() {;}
+  STFillerWrapper(const std::string& filename, int ifSel=-1, int beamSel=-1) :
+     STFiller(filename, ifSel, beamSel) {;}
+  STFillerWrapper(ScantableWrapper tbl) :
+    STFiller(tbl.getCP()){;}
+  ScantableWrapper getScantable() const {
+    return ScantableWrapper(getTable());
+  }
+};
+
+} // namespace
+#endif
Index: tags/Release2.1.0b/src/STFit.cpp
===================================================================
--- tags/Release2.1.0b/src/STFit.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STFit.cpp	(revision 1196)
@@ -0,0 +1,146 @@
+
+//
+// C++ Implementation: STFit
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#include <casa/Exceptions/Error.h>
+#include <tables/Tables/TableDesc.h>
+#include <tables/Tables/SetupNewTab.h>
+#include <tables/Tables/ScaColDesc.h>
+#include <tables/Tables/ArrColDesc.h>
+#include <tables/Tables/TableRecord.h>
+#include <tables/Tables/TableParse.h>
+#include <tables/Tables/TableRow.h>
+#include <casa/Containers/RecordField.h>
+
+#include "MathUtils.h"
+#include "STFitEntry.h"
+#include "STFit.h"
+
+
+using namespace casa;
+
+namespace asap {
+
+const casa::String STFit::name_ = "FIT";
+
+STFit::STFit(const Scantable& parent) :
+  STSubTable( parent, name_ )
+{
+  setup();
+}
+
+STFit& asap::STFit::operator =( const STFit & other )
+{
+  if ( this != &other ) {
+    static_cast<STSubTable&>(*this) = other;
+    funcCol_.attach(table_,"FUNCTIONS");
+    compCol_.attach(table_,"COMPONENTS");
+    parCol_.attach(table_,"PARAMETERS");
+    maskCol_.attach(table_,"PARMASKS");
+    frameCol_.attach(table_,"FRAMEINFO");
+  }
+  return *this;
+}
+
+asap::STFit::STFit( casa::Table tab ) : STSubTable(tab, name_)
+{
+    funcCol_.attach(table_,"FUNCTIONS");
+    compCol_.attach(table_,"COMPONENTS");
+    parCol_.attach(table_,"PARAMETERS");
+    maskCol_.attach(table_,"PARMASKS");
+    frameCol_.attach(table_,"FRAMEINFO");
+}
+
+STFit::~STFit()
+{
+}
+
+void asap::STFit::setup( )
+{
+  // add to base class table
+  table_.addColumn(ArrayColumnDesc<String>("FUNCTIONS"));
+  table_.addColumn(ArrayColumnDesc<Int>("COMPONENTS"));
+  table_.addColumn(ArrayColumnDesc<Double>("PARAMETERS"));
+  table_.addColumn(ArrayColumnDesc<Bool>("PARMASKS"));
+  table_.addColumn(ArrayColumnDesc<String>("FRAMEINFO"));
+
+  // new cached columns
+  funcCol_.attach(table_,"FUNCTIONS");
+  compCol_.attach(table_,"COMPONENTS");
+  parCol_.attach(table_,"PARAMETERS");
+  maskCol_.attach(table_,"PARMASKS");
+  frameCol_.attach(table_,"FRAMEINFO");
+}
+
+uInt STFit::addEntry( const STFitEntry& fit, Int id )
+{
+  uInt rno = table_.nrow();
+  uInt resultid = 0;
+  bool foundentry = false;
+  // replace
+  if ( id > -1 ) {
+    Table t = table_(table_.col("ID") == id );
+    if (t.nrow() > 0) {
+      rno = t.rowNumbers(table_)[0];
+      resultid = id;
+      foundentry = true;
+    }
+  }
+  // doesn't exist
+  if ( rno > 0  && !foundentry ) {
+    idCol_.get(rno-1, resultid);
+    resultid++;
+  }
+  // add new row if new id
+  if ( !foundentry ) table_.addRow();
+  funcCol_.put(rno, mathutil::toVectorString(fit.getFunctions()));
+  compCol_.put(rno, Vector<Int>(fit.getComponents()));
+  parCol_.put(rno, Vector<Double>(fit.getParameters()));
+  maskCol_.put(rno, Vector<Bool>(fit.getParmasks()));
+  frameCol_.put(rno, mathutil::toVectorString(fit.getFrameinfo()));
+  idCol_.put(rno, resultid);
+  return resultid;
+}
+
+void STFit::getEntry( STFitEntry& fit, uInt id ) const
+{
+  Table t = table_(table_.col("ID") == Int(id) );
+  if (t.nrow() == 0 ) {
+    throw(AipsError("STFit::getEntry - id out of range"));
+  }
+  ROTableRow row(t);
+  // get first row - there should only be one matching id
+  const TableRecord& rec = row.get(0);
+  std::vector<std::string> outstr;
+  Vector<String> vec;
+  rec.get("FUNCTIONS", vec);
+  fit.setFunctions(mathutil::tovectorstring(vec));
+  Vector<Int> ivec;
+  std::vector<int> istl;
+  rec.get("COMPONENTS", ivec);
+  ivec.tovector(istl);
+  fit.setComponents(istl);
+  Vector<Double> dvec;
+  std::vector<double> dstl;
+  rec.get("PARAMETERS", dvec);
+  dvec.tovector(dstl);
+  fit.setParameters(dstl);
+  Vector<Bool> bvec;
+  std::vector<bool> bstl;
+  rec.get("PARMASKS", bvec);
+  bvec.tovector(bstl);
+  fit.setParmasks(bstl);
+  vec.resize();
+  rec.get("FRAMEINFO", vec);
+  fit.setFrameinfo(mathutil::tovectorstring(vec));
+}
+
+} //namespace
Index: tags/Release2.1.0b/src/STFit.h
===================================================================
--- tags/Release2.1.0b/src/STFit.h	(revision 1196)
+++ tags/Release2.1.0b/src/STFit.h	(revision 1196)
@@ -0,0 +1,57 @@
+//
+// C++ Interface: STFit
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPSTFIT_H
+#define ASAPSTFIT_H
+
+#include <casa/aips.h>
+#include <casa/BasicSL/String.h>
+#include <tables/Tables/Table.h>
+#include <tables/Tables/ScalarColumn.h>
+#include <tables/Tables/ArrayColumn.h>
+
+#include "STSubTable.h"
+namespace asap {
+
+class STFitEntry;
+/**
+The Fit subtable of the Scantable
+
+@author Malte Marquarding
+*/
+class STFit : public STSubTable {
+public:
+  STFit() {;}
+  STFit(casa::Table tab);
+  STFit( const Scantable& parent);
+
+  virtual ~STFit();
+
+  STFit& operator=(const STFit& other);
+
+  casa::uInt addEntry( const STFitEntry& fit, casa::Int id=-1 );
+  void getEntry( STFitEntry& fit, casa::uInt id ) const;
+
+  const casa::String& name() const { return name_; }
+
+private:
+  void setup();
+  static const casa::String name_;
+  casa::ArrayColumn<casa::String> funcCol_;
+  casa::ArrayColumn<casa::Int> compCol_;
+  casa::ArrayColumn<casa::Double> parCol_;
+  casa::ArrayColumn<casa::Bool> maskCol_;
+  casa::ArrayColumn<casa::String> frameCol_;
+};
+
+}
+
+#endif
Index: tags/Release2.1.0b/src/STFitEntry.cpp
===================================================================
--- tags/Release2.1.0b/src/STFitEntry.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STFitEntry.cpp	(revision 1196)
@@ -0,0 +1,36 @@
+//
+// C++ Implementation: STFitEntry
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <Malte.Marquarding@csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#include "STFitEntry.h"
+
+namespace asap {
+
+STFitEntry::STFitEntry()
+{
+}
+STFitEntry::STFitEntry(const STFitEntry& other)
+{
+  if ( this != &other ) {
+    this->functions_ = other.functions_;
+    this->components_ = other.components_;
+    this->parameters_ = other.parameters_;
+    this->parmasks_ = other.parmasks_;
+    this->frameinfo_ = other.frameinfo_;
+  }
+}
+
+
+STFitEntry::~STFitEntry()
+{
+}
+
+
+}
Index: tags/Release2.1.0b/src/STFitEntry.h
===================================================================
--- tags/Release2.1.0b/src/STFitEntry.h	(revision 1196)
+++ tags/Release2.1.0b/src/STFitEntry.h	(revision 1196)
@@ -0,0 +1,59 @@
+//
+// C++ Interface: STFitEntry
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <Malte.Marquarding@csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPSTFITENTRY_H
+#define ASAPSTFITENTRY_H
+
+#include <vector>
+#include <string>
+
+namespace asap {
+
+/**
+This is a helper object for transporting Fit parameters
+
+@author Malte Marquarding
+*/
+class STFitEntry{
+public:
+  STFitEntry();
+  STFitEntry(const STFitEntry& other);
+
+  ~STFitEntry();
+
+  void setFunctions(const std::vector<std::string>& f)
+    { functions_ = f; }
+  void setComponents(const std::vector<int>& c)
+    { components_ = c; }
+  void setParameters(const std::vector<double>& p)
+    { parameters_ = p; }
+  void setParmasks(const std::vector<bool>& m)
+    { parmasks_ = m; }
+  void setFrameinfo(const std::vector<std::string>& f)
+    { frameinfo_ = f; }
+
+  std::vector<std::string> getFunctions() const { return functions_; }
+  std::vector<int> getComponents() const { return components_; }
+  std::vector<double> getParameters() const { return parameters_; }
+  std::vector<bool> getParmasks() const { return parmasks_; }
+  std::vector<std::string> getFrameinfo() const { return frameinfo_; }
+
+private:
+  std::vector<std::string> functions_;
+  std::vector<int> components_;
+  std::vector<double> parameters_;
+  std::vector<bool> parmasks_;
+  std::vector<std::string> frameinfo_;
+};
+
+}
+
+#endif
Index: tags/Release2.1.0b/src/STFitter.cpp
===================================================================
--- tags/Release2.1.0b/src/STFitter.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STFitter.cpp	(revision 1196)
@@ -0,0 +1,335 @@
+//#---------------------------------------------------------------------------
+//# Fitter.cc: A Fitter class for spectra
+//#--------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+#include <casa/aips.h>
+#include <casa/Arrays/ArrayMath.h>
+#include <casa/Arrays/ArrayLogical.h>
+#include <scimath/Fitting.h>
+#include <scimath/Fitting/LinearFit.h>
+#include <scimath/Functionals/CompiledFunction.h>
+#include <scimath/Functionals/CompoundFunction.h>
+#include <scimath/Functionals/Gaussian1D.h>
+#include <scimath/Functionals/Polynomial.h>
+#include <scimath/Mathematics/AutoDiff.h>
+#include <scimath/Mathematics/AutoDiffMath.h>
+#include <scimath/Fitting/NonLinearFitLM.h>
+#include <components/SpectralComponents/SpectralEstimate.h>
+
+#include "STFitter.h"
+
+using namespace asap;
+using namespace casa;
+
+Fitter::Fitter()
+{
+}
+
+Fitter::~Fitter()
+{
+  reset();
+}
+
+void Fitter::clear()
+{
+  for (uInt i=0;i< funcs_.nelements();++i) {
+    delete funcs_[i]; funcs_[i] = 0;
+  }
+  funcs_.resize(0,True);
+  parameters_.resize();
+  error_.resize();
+  thefit_.resize();
+  estimate_.resize();
+  chisquared_ = 0.0;
+}
+
+void Fitter::reset()
+{
+  clear();
+  x_.resize();
+  y_.resize();
+  m_.resize();
+}
+
+
+bool Fitter::computeEstimate() {
+  if (x_.nelements() == 0 || y_.nelements() == 0)
+    throw (AipsError("No x/y data specified."));
+
+  if (dynamic_cast<Gaussian1D<Float>* >(funcs_[0]) == 0)
+    return false;
+  uInt n = funcs_.nelements();
+  SpectralEstimate estimator(n);
+  estimator.setQ(5);
+  Int mn,mx;
+  mn = 0;
+  mx = m_.nelements()-1;
+  for (uInt i=0; i<m_.nelements();++i) {
+    if (m_[i]) {
+      mn = i;
+      break;
+    }
+  }
+  for (uInt j=m_.nelements()-1; j>=0;--j) {
+    if (m_[j]) {
+      mx = j;
+      break;
+    }
+  }
+  //mn = 0+x_.nelements()/10;
+  //mx = x_.nelements()-x_.nelements()/10;
+  estimator.setRegion(mn,mx);
+  //estimator.setWindowing(True);
+  SpectralList listGauss = estimator.estimate(x_, y_);
+  parameters_.resize(n*3);
+  Gaussian1D<Float>* g = 0;
+  for (uInt i=0; i<n;i++) {
+    g = dynamic_cast<Gaussian1D<Float>* >(funcs_[i]);
+    if (g) {
+      (*g)[0] = listGauss[i].getAmpl();
+      (*g)[1] = listGauss[i].getCenter();
+      (*g)[2] = listGauss[i].getFWHM();
+    }
+  }
+  estimate_.resize();
+  listGauss.evaluate(estimate_,x_);
+  return true;
+}
+
+std::vector<float> Fitter::getEstimate() const
+{
+  if (estimate_.nelements() == 0)
+    throw (AipsError("No estimate set."));
+  std::vector<float> stlout;
+  estimate_.tovector(stlout);
+  return stlout;
+}
+
+
+bool Fitter::setExpression(const std::string& expr, int ncomp)
+{
+  clear();
+  if (expr == "gauss") {
+    if (ncomp < 1) throw (AipsError("Need at least one gaussian to fit."));
+    funcs_.resize(ncomp);
+    for (Int k=0; k<ncomp; ++k) {
+      funcs_[k] = new Gaussian1D<Float>();
+    }
+  } else if (expr == "poly") {
+    funcs_.resize(1);
+    funcs_[0] = new Polynomial<Float>(ncomp);
+  } else {
+    cerr << " compiled functions not yet implemented" << endl;
+    //funcs_.resize(1);
+    //funcs_[0] = new CompiledFunction<Float>();
+    //funcs_[0]->setFunction(String(expr));
+    return false;
+  }
+  return true;
+}
+
+bool Fitter::setData(std::vector<float> absc, std::vector<float> spec,
+                       std::vector<bool> mask)
+{
+    x_.resize();
+    y_.resize();
+    m_.resize();
+    // convert std::vector to casa Vector
+    Vector<Float> tmpx(absc);
+    Vector<Float> tmpy(spec);
+    Vector<Bool> tmpm(mask);
+    AlwaysAssert(tmpx.nelements() == tmpy.nelements(), AipsError);
+    x_ = tmpx;
+    y_ = tmpy;
+    m_ = tmpm;
+    return true;
+}
+
+std::vector<float> Fitter::getResidual() const
+{
+    if (residual_.nelements() == 0)
+        throw (AipsError("Function not yet fitted."));
+    std::vector<float> stlout;
+    residual_.tovector(stlout);
+    return stlout;
+}
+
+std::vector<float> Fitter::getFit() const
+{
+    Vector<Float> out = thefit_;
+    std::vector<float> stlout;
+    out.tovector(stlout);
+    return stlout;
+
+}
+
+std::vector<float> Fitter::getErrors() const
+{
+    Vector<Float> out = error_;
+    std::vector<float> stlout;
+    out.tovector(stlout);
+    return stlout;
+}
+
+bool Fitter::setParameters(std::vector<float> params)
+{
+    Vector<Float> tmppar(params);
+    if (funcs_.nelements() == 0)
+        throw (AipsError("Function not yet set."));
+    if (parameters_.nelements() > 0 && tmppar.nelements() != parameters_.nelements())
+        throw (AipsError("Number of parameters inconsistent with function."));
+    if (parameters_.nelements() == 0)
+        parameters_.resize(tmppar.nelements());
+        fixedpar_.resize(tmppar.nelements());
+        fixedpar_ = False;
+    if (dynamic_cast<Gaussian1D<Float>* >(funcs_[0]) != 0) {
+        uInt count = 0;
+        for (uInt j=0; j < funcs_.nelements(); ++j) {
+            for (uInt i=0; i < funcs_[j]->nparameters(); ++i) {
+                (funcs_[j]->parameters())[i] = tmppar[count];
+                parameters_[count] = tmppar[count];
+                ++count;
+            }
+        }
+    } else if (dynamic_cast<Polynomial<Float>* >(funcs_[0]) != 0) {
+        for (uInt i=0; i < funcs_[0]->nparameters(); ++i) {
+            parameters_[i] = tmppar[i];
+            (funcs_[0]->parameters())[i] =  tmppar[i];
+        }
+    }
+    return true;
+}
+
+bool Fitter::setFixedParameters(std::vector<bool> fixed)
+{
+    Vector<Bool> tmp(fixed);
+    if (funcs_.nelements() == 0)
+        throw (AipsError("Function not yet set."));
+    if (fixedpar_.nelements() > 0 && tmp.nelements() != fixedpar_.nelements())
+        throw (AipsError("Number of mask elements inconsistent with function."));
+    if (dynamic_cast<Gaussian1D<Float>* >(funcs_[0]) != 0) {
+        uInt count = 0;
+        for (uInt j=0; j < funcs_.nelements(); ++j) {
+            for (uInt i=0; i < funcs_[j]->nparameters(); ++i) {
+                funcs_[j]->mask(i) = !tmp[count];
+                fixedpar_[count] = !tmp[count];
+                ++count;
+            }
+        }
+    } else if (dynamic_cast<Polynomial<Float>* >(funcs_[0]) != 0) {
+        for (uInt i=0; i < funcs_[0]->nparameters(); ++i) {
+            fixedpar_[i] = tmp[i];
+            funcs_[0]->mask(i) =  tmp[i];
+        }
+    }
+    //fixedpar_ = !tmpmsk;
+    return true;
+}
+
+std::vector<float> Fitter::getParameters() const {
+    Vector<Float> out = parameters_;
+    std::vector<float> stlout;
+    out.tovector(stlout);
+    return stlout;
+}
+
+std::vector<bool> Fitter::getFixedParameters() const {
+  Vector<Bool> out(parameters_.nelements());
+  if (fixedpar_.nelements() == 0) {
+    out = False;
+    //throw (AipsError("No parameter mask set."));
+  } else {
+    out = fixedpar_;
+  }
+  std::vector<bool> stlout;
+  out.tovector(stlout);
+  return stlout;
+}
+
+float Fitter::getChisquared() const {
+    return chisquared_;
+}
+
+bool Fitter::fit() {
+  NonLinearFitLM<Float> fitter;
+  CompoundFunction<Float> func;
+
+  uInt n = funcs_.nelements();
+  for (uInt i=0; i<n; ++i) {
+    func.addFunction(*funcs_[i]);
+  }
+
+  fitter.setFunction(func);
+  fitter.setMaxIter(50+n*10);
+  // Convergence criterium
+  fitter.setCriteria(0.001);
+
+  // Fit
+  Vector<Float> sigma(x_.nelements());
+  sigma = 1.0;
+
+  parameters_.resize();
+  parameters_ = fitter.fit(x_, y_, sigma, &m_);
+  if ( !fitter.converged() ) {
+     return false;
+  }
+  std::vector<float> ps;
+  parameters_.tovector(ps);
+  setParameters(ps);
+
+  error_.resize();
+  error_ = fitter.errors();
+
+  chisquared_ = fitter.getChi2();
+
+  residual_.resize();
+  residual_ =  y_;
+  fitter.residual(residual_,x_);
+
+  // use fitter.residual(model=True) to get the model
+  thefit_.resize(x_.nelements());
+  fitter.residual(thefit_,x_,True);
+  return true;
+}
+
+
+std::vector<float> Fitter::evaluate(int whichComp) const
+{
+  std::vector<float> stlout;
+  uInt idx = uInt(whichComp);
+  Float y;
+  if ( idx < funcs_.nelements() ) {
+    for (uInt i=0; i<x_.nelements(); ++i) {
+      y = (*funcs_[idx])(x_[i]);
+      stlout.push_back(float(y));
+    }
+  }
+  return stlout;
+}
+
Index: tags/Release2.1.0b/src/STFitter.h
===================================================================
--- tags/Release2.1.0b/src/STFitter.h	(revision 1196)
+++ tags/Release2.1.0b/src/STFitter.h	(revision 1196)
@@ -0,0 +1,88 @@
+//#---------------------------------------------------------------------------
+//# Fitter.h: A Fitter class for spectra
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id:$
+//#---------------------------------------------------------------------------
+#ifndef STFITTER_H
+#define STFITTER_H
+
+#include <string>
+#include <vector>
+
+#include <casa/Arrays/Vector.h>
+#include <casa/Containers/Block.h>
+#include <scimath/Functionals/Function.h>
+#include <scimath/Functionals/CompoundFunction.h>
+
+namespace asap {
+
+class Fitter {
+public:
+  Fitter();
+  virtual ~Fitter();
+  // allowed "gauss" and "poly". ncomp is either numvber of gaussions
+  // or order of the polynomial
+  bool setExpression(const std::string& expr, int ncomp=1);
+  bool setData(std::vector<float> absc, std::vector<float> spec,
+	       std::vector<bool> mask);
+  bool setParameters(std::vector<float> params);
+  bool setFixedParameters(std::vector<bool> fixed);
+
+  std::vector<float> getResidual() const;
+  std::vector<float> getFit() const;
+  std::vector<float> getParameters() const;
+  std::vector<bool> getFixedParameters() const;
+
+  std::vector<float> getEstimate() const;
+  std::vector<float> getErrors() const;
+  float getChisquared() const;
+  void reset();
+  bool fit();
+  bool computeEstimate();
+
+  std::vector<float> evaluate(int whichComp) const;
+private:
+  void clear();
+  casa::Vector<casa::Float> x_;
+  casa::Vector<casa::Float> y_;
+  casa::Vector<casa::Bool> m_;
+  casa::PtrBlock<casa::Function<casa::Float>* > funcs_;
+  //Bool estimateSet_;
+  casa::Float chisquared_;
+  casa::Vector<casa::Float> parameters_;
+  casa::Vector<casa::Bool> fixedpar_;
+
+  casa::Vector<casa::Float> error_;
+  casa::Vector<casa::Float> thefit_;
+  casa::Vector<casa::Float> residual_;
+  casa::Vector<casa::Float> estimate_;
+};
+
+} // namespace
+
+#endif
Index: tags/Release2.1.0b/src/STFocus.cpp
===================================================================
--- tags/Release2.1.0b/src/STFocus.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STFocus.cpp	(revision 1196)
@@ -0,0 +1,178 @@
+//
+// C++ Implementation: STFocus
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#include <casa/Exceptions/Error.h>
+#include <tables/Tables/TableDesc.h>
+#include <tables/Tables/SetupNewTab.h>
+#include <tables/Tables/ScaColDesc.h>
+#include <tables/Tables/TableRecord.h>
+#include <tables/Tables/TableParse.h>
+#include <tables/Tables/TableRow.h>
+#include <casa/Containers/RecordField.h>
+
+#include "STFocus.h"
+
+
+using namespace casa;
+
+namespace asap {
+
+const casa::String STFocus::name_ = "FOCUS";
+
+STFocus::STFocus(const Scantable& parent ) :
+  STSubTable( parent, name_ )
+{
+  setup();
+}
+
+asap::STFocus::STFocus( casa::Table tab ) : STSubTable(tab, name_)
+{
+  rotationCol_.attach(table_,"ROTATION");
+  axisCol_.attach(table_,"AXIS");
+  tanCol_.attach(table_,"TAN");
+  handCol_.attach(table_,"HAND");
+  userCol_.attach(table_,"USERPHASE");
+  mountCol_.attach(table_,"MOUNT");
+  xyphCol_.attach(table_,"XYPHASE");
+  xyphoffCol_.attach(table_,"XYPHASEOFFSET");
+}
+
+STFocus::~STFocus()
+{
+}
+
+STFocus& asap::STFocus::operator =( const STFocus & other )
+{
+  if (this != &other) {
+    static_cast<STSubTable&>(*this) = other;
+    rotationCol_.attach(table_,"ROTATION");
+    axisCol_.attach(table_,"AXIS");
+    tanCol_.attach(table_,"TAN");
+    handCol_.attach(table_,"HAND");
+    userCol_.attach(table_,"USERPHASE");
+    mountCol_.attach(table_,"MOUNT");
+    xyphCol_.attach(table_,"XYPHASE");
+    xyphoffCol_.attach(table_,"XYPHASEOFFSET");
+  }
+  return *this;
+}
+void asap::STFocus::setup( )
+{
+  // add to base class table
+  table_.addColumn(ScalarColumnDesc<Float>("ROTATION"));
+  table_.addColumn(ScalarColumnDesc<Float>("AXIS"));
+  table_.addColumn(ScalarColumnDesc<Float>("TAN"));
+  table_.addColumn(ScalarColumnDesc<Float>("HAND"));
+  table_.addColumn(ScalarColumnDesc<Float>("USERPHASE"));
+  table_.addColumn(ScalarColumnDesc<Float>("MOUNT"));
+  table_.addColumn(ScalarColumnDesc<Float>("XYPHASE"));
+  table_.addColumn(ScalarColumnDesc<Float>("XYPHASEOFFSET"));
+
+  // new cached columns
+  rotationCol_.attach(table_,"ROTATION");
+  axisCol_.attach(table_,"AXIS");
+  tanCol_.attach(table_,"TAN");
+  handCol_.attach(table_,"HAND");
+  userCol_.attach(table_,"USERPHASE");
+  mountCol_.attach(table_,"MOUNT");
+  xyphCol_.attach(table_,"XYPHASE");
+  xyphoffCol_.attach(table_,"XYPHASEOFFSET");
+}
+
+uInt STFocus::addEntry( Float fax, Float ftan, Float frot, Float hand,
+                        Float user, Float mount,
+                        Float xyphase, Float xyphaseoffset)
+{
+  Table result = table_( near(table_.col("ROTATION"), frot)
+                    && near(table_.col("AXIS"), fax)
+                    && near(table_.col("TAN"), ftan)
+                    && near(table_.col("HAND"), hand)
+                    && near(table_.col("USERPHASE"), user)
+                    && near(table_.col("MOUNT"), mount)
+                    && near(table_.col("XYPHASE"), xyphase)
+                    && near(table_.col("XYPHASEOFFSET"), xyphaseoffset)
+                    );
+  uInt resultid = 0;
+  if ( result.nrow() > 0) {
+    ROScalarColumn<uInt> c(result, "ID");
+    c.get(0, resultid);
+  } else {
+    uInt rno = table_.nrow();
+    table_.addRow();
+    // get last assigned _id and increment
+    if ( rno > 0 ) {
+      idCol_.get(rno-1, resultid);
+      resultid++;
+    }
+    rotationCol_.put(rno, frot);
+    axisCol_.put(rno, fax);
+    tanCol_.put(rno, ftan);
+    handCol_.put(rno, hand);
+    userCol_.put(rno, user);
+    mountCol_.put(rno, mount);
+    xyphCol_.put(rno, xyphase);
+    xyphoffCol_.put(rno, xyphaseoffset);
+    idCol_.put(rno, resultid);
+  }
+  return resultid;
+}
+
+void asap::STFocus::getEntry( Float& rotation, Float& angle, Float& ftan,
+                              Float& hand, Float& user, Float& mount,
+                              Float& xyphase, Float& xyphaseoffset,
+                              uInt id) const
+{
+  Table t = table_(table_.col("ID") == Int(id) );
+  if (t.nrow() == 0 ) {
+    throw(AipsError("STFocus::getEntry - id out of range"));
+  }
+  ROTableRow row(t);
+  // get first row - there should only be one matching id
+  const TableRecord& rec = row.get(0);
+  rotation = rec.asFloat("ROTATION");
+  angle = rec.asFloat("AXIS");
+  ftan = rec.asFloat("TAN");
+  hand = rec.asFloat("HAND");
+  user = rec.asFloat("USERPHASE");
+  mount = rec.asFloat("MOUNT");
+  xyphase = rec.asFloat("XYPHASE");
+  xyphaseoffset = rec.asFloat("XYPHASEOFFSET");
+}
+
+
+casa::Float asap::STFocus::getTotalFeedAngle( casa::uInt id ) const
+{
+  Float total = 0.0f;
+  Table t = table_(table_.col("ID") == Int(id) );
+  if (t.nrow() == 0 ) {
+    throw(AipsError("STFocus::getEntry - id out of range"));
+  }
+  ROTableRow row(t);
+  // get first row - there should only be one matching id
+  const TableRecord& rec = row.get(0);
+  total += rec.asFloat("ROTATION");
+  total += rec.asFloat("USERPHASE");
+  total += rec.asFloat("MOUNT");
+  return total;
+}
+}
+
+casa::Float asap::STFocus::getFeedHand( casa::uInt id ) const
+{
+  Table t = table_(table_.col("ID") == Int(id) );
+  if (t.nrow() == 0 ) {
+    throw(AipsError("STFocus::getEntry - id out of range"));
+  }
+  ROTableRow row(t);
+  const TableRecord& rec = row.get(0);
+  return rec.asFloat("HAND");
+}
+
Index: tags/Release2.1.0b/src/STFocus.h
===================================================================
--- tags/Release2.1.0b/src/STFocus.h	(revision 1196)
+++ tags/Release2.1.0b/src/STFocus.h	(revision 1196)
@@ -0,0 +1,66 @@
+//
+// C++ Interface: STFocus
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPSTFOCUS_H
+#define ASAPSTFOCUS_H
+
+#include <casa/aips.h>
+#include <casa/BasicSL/String.h>
+#include <tables/Tables/Table.h>
+#include <tables/Tables/ScalarColumn.h>
+
+#include "STSubTable.h"
+
+namespace asap {
+
+/**
+The Focus subtable of the Scantable
+
+@author Malte Marquarding
+*/
+class STFocus : public STSubTable {
+public:
+  STFocus() {;}
+  STFocus(casa::Table tab);
+  STFocus( const Scantable& parent );
+
+  virtual ~STFocus();
+
+  STFocus& operator=(const STFocus& other);
+
+  casa::uInt addEntry( casa::Float faxis, casa::Float ftan,
+                       casa::Float frot, casa::Float hand=1.0f,
+                       casa::Float mount=0.0f, casa::Float user=0.0f,
+                       casa::Float xyphase=0.0f, casa::Float xyphaseoffset=0.0f);
+
+  void getEntry( casa::Float& fax, casa::Float& ftan,
+                 casa::Float& frot, casa::Float& hand,
+                 casa::Float& mount, casa::Float& user,
+                 casa::Float& xyphase, casa::Float& xyphaseoffset,
+                 casa::uInt id) const;
+
+  casa::Float getTotalFeedAngle(casa::uInt id) const;
+  casa::Float getFeedHand(casa::uInt id) const;
+
+  const casa::String& name() const { return name_; }
+
+private:
+  void setup();
+  static const casa::String name_;
+  casa::ScalarColumn<casa::Float> rotationCol_, axisCol_,
+                                  tanCol_,handCol_,
+                                  mountCol_,userCol_,
+                                  xyphCol_,xyphoffCol_;
+};
+
+}
+
+#endif
Index: tags/Release2.1.0b/src/STFrequencies.cpp
===================================================================
--- tags/Release2.1.0b/src/STFrequencies.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STFrequencies.cpp	(revision 1196)
@@ -0,0 +1,400 @@
+//
+// C++ Implementation: STFrequencies
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#include <casa/iostream.h>
+#include <casa/iomanip.h>
+#include <casa/Exceptions/Error.h>
+#include <casa/Containers/RecordField.h>
+#include <casa/Arrays/IPosition.h>
+
+#include <tables/Tables/TableDesc.h>
+#include <tables/Tables/SetupNewTab.h>
+#include <tables/Tables/ScaColDesc.h>
+#include <tables/Tables/TableRecord.h>
+#include <tables/Tables/TableParse.h>
+#include <tables/Tables/TableRow.h>
+
+#include <coordinates/Coordinates/CoordinateSystem.h>
+#include <coordinates/Coordinates/CoordinateUtil.h>
+
+#include "STFrequencies.h"
+
+
+using namespace casa;
+
+namespace asap {
+
+const String STFrequencies::name_ = "FREQUENCIES";
+
+STFrequencies::STFrequencies(const Scantable& parent) :
+  STSubTable(parent, name_)
+{
+  setup();
+}
+
+asap::STFrequencies::STFrequencies( casa::Table tab ) :
+  STSubTable(tab, name_)
+{
+  refpixCol_.attach(table_,"REFPIX");
+  refvalCol_.attach(table_,"REFVAL");
+  incrCol_.attach(table_,"INCREMENT");
+
+}
+
+STFrequencies::~STFrequencies()
+{
+}
+
+STFrequencies & asap::STFrequencies::operator =( const STFrequencies & other )
+{
+  if ( this != &other ) {
+    static_cast<STSubTable&>(*this) = other;
+    refpixCol_.attach(table_,"REFPIX");
+    refvalCol_.attach(table_,"REFVAL");
+    incrCol_.attach(table_,"INCREMENT");
+  }
+  return *this;
+}
+
+void STFrequencies::setup( )
+{
+  // add to base class table
+  table_.addColumn(ScalarColumnDesc<Double>("REFPIX"));
+  table_.addColumn(ScalarColumnDesc<Double>("REFVAL"));
+  table_.addColumn(ScalarColumnDesc<Double>("INCREMENT"));
+
+  table_.rwKeywordSet().define("FRAME", String("TOPO"));
+  table_.rwKeywordSet().define("BASEFRAME", String("TOPO"));
+  table_.rwKeywordSet().define("EQUINOX",String( "J2000"));
+  table_.rwKeywordSet().define("UNIT", String(""));
+  table_.rwKeywordSet().define("DOPPLER", String("RADIO"));
+
+  // new cached columns
+  refpixCol_.attach(table_,"REFPIX");
+  refvalCol_.attach(table_,"REFVAL");
+  incrCol_.attach(table_,"INCREMENT");
+}
+
+uInt STFrequencies::addEntry( Double refpix, Double refval, Double inc )
+{
+  // test if this already exists
+  Table result = table_( near(table_.col("REFVAL"), refval)
+                    && near(table_.col("REFPIX"), refpix)
+                    && near(table_.col("INCREMENT"), inc) );
+  uInt resultid = 0;
+  if ( result.nrow() > 0) {
+    ROScalarColumn<uInt> c(result, "ID");
+    c.get(0, resultid);
+
+  } else {
+    uInt rno = table_.nrow();
+    table_.addRow();
+    // get last assigned freq_id and increment
+    if ( rno > 0 ) {
+      idCol_.get(rno-1, resultid);
+      resultid++;
+    }
+    refpixCol_.put(rno, refpix);
+    refvalCol_.put(rno, refval);
+    incrCol_.put(rno, inc);
+    idCol_.put(rno, resultid);
+  }
+  return resultid;
+}
+
+
+
+void STFrequencies::getEntry( Double& refpix, Double& refval, Double& inc,
+                              uInt id )
+{
+  Table t = table_(table_.col("ID") == Int(id) );
+  if (t.nrow() == 0 ) {
+    throw(AipsError("STFrequencies::getEntry - freqID out of range"));
+  }
+  ROTableRow row(t);
+  // get first row - there should only be one matching id
+  const TableRecord& rec = row.get(0);
+  refpix = rec.asDouble("REFPIX");
+  refval = rec.asDouble("REFVAL");
+  inc = rec.asDouble("INCREMENT");
+}
+
+SpectralCoordinate STFrequencies::getSpectralCoordinate( uInt id ) const
+{
+  Table t = table_(table_.col("ID") == Int(id) );
+
+  if (t.nrow() == 0 ) {
+    throw(AipsError("STFrequencies::getSpectralCoordinate - ID out of range"));
+  }
+
+  // get the data
+  ROTableRow row(t);
+  // get first row - there should only be one matching id
+  const TableRecord& rec = row.get(0);
+
+  return SpectralCoordinate( getFrame(true), rec.asDouble("REFVAL"),
+                             rec.asDouble("INCREMENT"),
+                             rec.asDouble("REFPIX"));
+}
+
+SpectralCoordinate
+  asap::STFrequencies::getSpectralCoordinate( const MDirection& md,
+                                              const MPosition& mp,
+                                              const MEpoch& me,
+                                              Double restfreq, uInt id ) const
+{
+  SpectralCoordinate spc = getSpectralCoordinate(id);
+  spc.setRestFrequency(restfreq, True);
+  if ( !spc.setReferenceConversion(getFrame(), me, mp, md) ) {
+    throw(AipsError("Couldn't convert frequency frame."));
+  }
+  String unitstr = getUnitString();
+  if ( !unitstr.empty() ) {
+    Unit unitu(unitstr);
+    if ( unitu == Unit("Hz") ) {
+      Vector<String> wau(1); wau = unitu.getName();
+      spc.setWorldAxisUnits(wau);
+    } else {
+      spc.setVelocity(unitstr, getDoppler());
+    }
+  }
+  return spc;
+}
+
+
+void STFrequencies::rescale( Float factor, const std::string& mode )
+{
+  TableRow row(table_);
+  TableRecord& outrec = row.record();
+  RecordFieldPtr<Double> rv(outrec, "REFVAL");
+  RecordFieldPtr<Double> rp(outrec, "REFPIX");
+  RecordFieldPtr<Double> inc(outrec, "INCREMENT");
+  for (uInt i=0; i<table_.nrow(); ++i) {
+
+    const TableRecord& rec = row.get(i);
+
+    SpectralCoordinate sc ( getFrame(true), rec.asDouble("REFVAL"),
+                            rec.asDouble("INCREMENT"), rec.asDouble("REFPIX") );
+
+    SpectralCoordinate scout;
+    if (mode == "BIN") {
+      scout = binCsys(sc, Int(factor));
+    } else if (mode == "RESAMPLE") {
+      scout = resampleCsys(sc, factor);
+    }
+    *rv = scout.referenceValue()[0];
+    *rp = scout.referencePixel()[0];
+    *inc = scout.increment()[0];
+    row.put(i);
+  }
+}
+
+SpectralCoordinate STFrequencies::binCsys(const SpectralCoordinate& sc,
+                                          Int factor)
+{
+  CoordinateSystem csys;
+  csys.addCoordinate(sc);
+  IPosition factors(1, factor);
+  CoordinateSystem binnedcs =
+    CoordinateUtil::makeBinnedCoordinateSystem(factors, csys, False);
+  return binnedcs.spectralCoordinate(0);
+}
+
+SpectralCoordinate STFrequencies::resampleCsys(const SpectralCoordinate& sc,
+                                               Float width)
+{
+  Vector<Float> offset(1,0.0);
+  Vector<Float> factors(1,1.0/width);
+  Vector<Int> newshape;
+  CoordinateSystem csys;
+  csys.addCoordinate(sc);
+  CoordinateSystem csys2 = csys.subImage(offset, factors, newshape);
+  return csys2.spectralCoordinate(0);
+}
+
+
+MFrequency::Types STFrequencies::getFrame(bool base) const
+{
+  // get the ref frame
+  String rf;
+  if ( base )
+    rf = table_.keywordSet().asString("BASEFRAME");
+  else
+    rf = table_.keywordSet().asString("FRAME");
+
+  // Create SpectralCoordinate (units Hz)
+  MFrequency::Types mft;
+  if (!MFrequency::getType(mft, rf)) {
+    ostringstream oss;
+    pushLog("WARNING: Frequency type unknown assuming TOPO");
+    mft = MFrequency::TOPO;
+  }
+
+  return mft;
+}
+
+std::string asap::STFrequencies::getFrameString( bool base ) const
+{
+  if ( base ) return table_.keywordSet().asString("BASEFRAME");
+  else return table_.keywordSet().asString("FRAME");
+}
+
+std::string asap::STFrequencies::getUnitString( ) const
+{
+  return table_.keywordSet().asString("UNIT");
+}
+
+Unit asap::STFrequencies::getUnit( ) const
+{
+  return Unit(table_.keywordSet().asString("UNIT"));
+}
+
+std::string asap::STFrequencies::getDopplerString( ) const
+{
+  return table_.keywordSet().asString("DOPPLER");
+}
+
+MDoppler::Types asap::STFrequencies::getDoppler( ) const
+{
+  String dpl = table_.keywordSet().asString("DOPPLER");
+
+  // Create SpectralCoordinate (units Hz)
+  MDoppler::Types mdt;
+  if (!MDoppler::getType(mdt, dpl)) {
+    throw(AipsError("Doppler type unknown"));
+  }
+  return mdt;
+}
+
+std::string asap::STFrequencies::print( int id )
+{
+  Table t;
+  ostringstream oss;
+  if ( id < 0 ) t = table_;
+  else  t = table_(table_.col("ID") == Int(id) );
+  ROTableRow row(t);
+  for (uInt i=0; i<t.nrow(); ++i) {
+    const TableRecord& rec = row.get(i);
+    oss <<  setw(8)
+    << t.keywordSet().asString("FRAME") << setw(16) << setprecision(8)
+    << rec.asDouble("REFVAL") << setw(7)
+    << rec.asDouble("REFPIX") << setw(12)
+    << rec.asDouble("INCREMENT") << endl;
+  }
+  return String(oss);
+}
+
+float STFrequencies::getRefFreq( uInt id, uInt channel )
+{
+  Table t = table_(table_.col("ID") == Int(id) );
+  if ( t.nrow() == 0 ) throw(AipsError("Selected Illegal frequency id"));
+  ROTableRow row(t);
+  const TableRecord& rec = row.get(0);
+  return (Double(channel/2) - rec.asDouble("REFPIX"))
+          * rec.asDouble("INCREMENT") + rec.asDouble("REFVAL");
+}
+
+bool asap::STFrequencies::conformant( const STFrequencies& other ) const
+{
+  const Record& r = table_.keywordSet();
+  const Record& ro = other.table_.keywordSet();
+  return ( r.asString("FRAME") == ro.asString("FRAME") &&
+           r.asString("EQUINOX") == ro.asString("EQUINOX") &&
+           r.asString("UNIT") == ro.asString("UNIT") &&
+           r.asString("DOPPLER") == ro.asString("DOPPLER")
+          );
+}
+
+std::vector< std::string > asap::STFrequencies::getInfo( ) const
+{
+  const Record& r = table_.keywordSet();
+  std::vector<std::string> out;
+  out.push_back(r.asString("UNIT"));
+  out.push_back(r.asString("FRAME"));
+  out.push_back(r.asString("DOPPLER"));
+  return out;
+}
+
+void asap::STFrequencies::setInfo( const std::vector< std::string >& theinfo )
+{
+  if ( theinfo.size() != 3 ) throw(AipsError("setInfo needs three parameters"));
+  try {
+    setUnit(theinfo[0]);
+    setFrame(theinfo[1]);
+    setDoppler(theinfo[2]);
+  } catch (AipsError& e) {
+    throw(e);
+  }
+}
+
+void asap::STFrequencies::setUnit( const std::string & unit )
+{
+  if (unit == "" || unit == "pixel" || unit == "channel" ) {
+    table_.rwKeywordSet().define("UNIT", "");
+  } else {
+    Unit u(unit);
+    if ( u == Unit("km/s") || u == Unit("Hz") )
+      table_.rwKeywordSet().define("UNIT", unit);
+    else {
+      throw(AipsError("Illegal spectral unit."));
+    }
+  }
+}
+
+void asap::STFrequencies::setFrame(MFrequency::Types frame, bool base )
+{
+  String f = MFrequency::showType(frame);
+  if (base)
+    table_.rwKeywordSet().define("BASEFRAME", f);
+  else
+    table_.rwKeywordSet().define("FRAME", f);
+
+}
+
+void asap::STFrequencies::setFrame( const std::string & frame, bool base )
+{
+  MFrequency::Types mdr;
+  if (!MFrequency::getType(mdr, frame)) {
+    Int a,b;const uInt* c;
+    const String* valid = MFrequency::allMyTypes(a, b, c);
+    Vector<String> ftypes(IPosition(1,a), valid);
+    ostringstream oss;
+    oss <<  String("Please specify a legal frequency type. Types are\n");
+    oss << ftypes;
+    String msg(oss);
+    throw(AipsError(msg));
+  } else {
+    if (base)
+      table_.rwKeywordSet().define("BASEFRAME", frame);
+    else
+      table_.rwKeywordSet().define("FRAME", frame);
+  }
+}
+
+void asap::STFrequencies::setDoppler( const std::string & doppler )
+{
+  MDoppler::Types mdt;
+  if (!MDoppler::getType(mdt, doppler)) {
+    Int a,b;const uInt* c;
+    const String* valid = MDoppler::allMyTypes(a, b, c);
+    Vector<String> ftypes(IPosition(1,a), valid);
+    ostringstream oss;
+    oss <<  String("Please specify a legal doppler type. Types are\n");
+    oss << ftypes;
+    String msg(oss);
+    throw(AipsError(msg));
+  } else {
+    table_.rwKeywordSet().define("DOPPLER", doppler);
+  }
+}
+
+
+} // namespace
Index: tags/Release2.1.0b/src/STFrequencies.h
===================================================================
--- tags/Release2.1.0b/src/STFrequencies.h	(revision 1196)
+++ tags/Release2.1.0b/src/STFrequencies.h	(revision 1196)
@@ -0,0 +1,176 @@
+//
+// C++ Interface: STFrequencies
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPSTFREQUENCIES_H
+#define ASAPSTFREQUENCIES_H
+
+#include <casa/aips.h>
+#include <casa/BasicSL/String.h>
+#include <coordinates/Coordinates/SpectralCoordinate.h>
+#include <tables/Tables/Table.h>
+#include <tables/Tables/ScalarColumn.h>
+
+#include "STSubTable.h"
+
+namespace asap {
+
+/**
+The Frequencies subtable of the Scantable
+
+@author Malte Marquarding
+@brief The frequency subtable of the Scantable
+*/
+class STFrequencies : public STSubTable {
+public:
+  STFrequencies() {;}
+  STFrequencies(casa::Table tab);
+  STFrequencies(const Scantable& parent);
+
+  virtual ~STFrequencies();
+
+  STFrequencies& operator=(const STFrequencies& other);
+
+  /**
+   * Add a new Entry to the Frequency subtable. This checks for duplicates.
+   * @param[in] refpix the reference pixel
+   * @param[in] refval the reference value
+   * @param[in] inc the increment
+   * @return an index into the frequency table
+   */
+  casa::uInt addEntry( casa::Double refpix, casa::Double refval,
+                       casa::Double inc );
+
+  /**
+   * Retrieve the frequency values for a specific id via references
+   * @param refpix the reference pixel
+   * @param refval the reference value
+   * @param inc the increment
+   * @param id the identifier
+   */
+  void getEntry( casa::Double& refpix, casa::Double& refval,
+                 casa::Double& inc, casa::uInt id );
+
+
+  bool conformant(const STFrequencies& other) const;
+
+  /**
+   * Retrieve  the frequency values as a casa::SpectralCoordinate
+   * @param freqID
+   * @return casa::SpectralCoordinate
+   */
+  casa::SpectralCoordinate getSpectralCoordinate( casa::uInt freqID ) const;
+
+  casa::SpectralCoordinate getSpectralCoordinate( const casa::MDirection& md,
+                                                  const casa::MPosition& mp,
+                                                  const casa::MEpoch& me,
+                                                  casa::Double restfreq,
+                                                  casa::uInt freqID
+                                                  ) const;
+
+  /**
+   * Return the unit of the frequency values
+   * @return casa::Unit
+   */
+  casa::Unit getUnit() const;
+  std::string getUnitString() const;
+
+  /**
+   * Return the doppler type of the values
+   * @return casa::MDoppler::Types
+   */
+  casa::MDoppler::Types getDoppler() const;
+  std::string getDopplerString() const;
+
+
+  /**
+   * Return the frame type, e.g MFrequency::TOPO
+   * @param base return the base frame or the user frame
+   * @return casa::MFrequency::Types
+   */
+  casa::MFrequency::Types getFrame(bool base=false) const;
+
+  /**
+   * Return a string representation of the frame type, e.g TOPO
+   * @param base return the base frame or the user frame
+   * @return the string representation of the frame
+   */
+  std::string getFrameString(bool base=false) const;
+
+  /**
+   * set the frequency frame from a string value
+   * @param frame a string identifier
+   */
+  void setFrame(const std::string& frame, bool base=false);
+  /**
+   * set the frequency frame from a casa::MFrequency::Types
+   * @param frame casa::MFrequency::Types
+   */
+  void setFrame(casa::MFrequency::Types frame, bool base=false);
+  void setUnit( const std::string & unit );
+  void setDoppler( const std::string & doppler );
+  /**
+   * rescale the whole table by a given factor
+   * @param factor the factor to bin or resample by
+   * @param mode the rescaling mode
+   * @li "BIN"
+   * @li "RESAMPLE"
+   */
+  void rescale(casa::Float factor, const std::string& mode);
+
+  /**
+   * get the reference frequency at a given channel for a specidif identifier
+   * @param id the identifier
+   * @param channel the channel number
+   * @return teh reference frequency
+   */
+  float getRefFreq(casa::uInt id, casa::uInt channel);
+
+
+  /**
+   * Rteun this table or s specific row as a string representation
+   * @param id the identifier. If id<0 all rows are returned
+   * @return a string
+   */
+  std::string print(int id=-1);
+
+  std::vector<std::string> getInfo() const;
+  void setInfo( const std::vector<std::string>& theinfo );
+
+  const casa::String& name() const { return name_; }
+
+private:
+
+  /**
+   * setup the the column structure of the casa::table
+   */
+  void setup();
+  /**
+   * the actual binning of the SpectralCoordinate as called by rescale
+   * @param sc
+   * @param factor the bin factor
+   * @return casa::SpectralCoordinate
+   */
+  casa::SpectralCoordinate binCsys(const casa::SpectralCoordinate& sc, casa::Int factor);
+  /**
+   * the actual resampling of the SpectralCoordinate as called by rescale
+   * @param sc
+   * @param width the resacle width. Can be decimal.
+   * @return
+   */
+  casa::SpectralCoordinate resampleCsys(const casa::SpectralCoordinate& sc, casa::Float width);
+
+  static const casa::String name_;
+  casa::ScalarColumn<casa::Double> refvalCol_, refpixCol_, incrCol_;
+};
+
+}
+
+#endif
Index: tags/Release2.1.0b/src/STHeader.cpp
===================================================================
--- tags/Release2.1.0b/src/STHeader.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STHeader.cpp	(revision 1196)
@@ -0,0 +1,120 @@
+//#---------------------------------------------------------------------------
+//# STHeader.cpp: A container class for single dish integrations
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id:
+//#---------------------------------------------------------------------------
+
+#include <casa/aips.h>
+#include <casa/iostream.h>
+#include <casa/iomanip.h>
+#include <casa/Exceptions.h>
+#include <casa/Utilities/Assert.h>
+#include <casa/Arrays/IPosition.h>
+#include <casa/Quanta/MVTime.h>
+
+
+
+#include "STDefs.h"
+#include "STHeader.h"
+
+using namespace casa;
+using namespace asap;
+
+
+
+bool STHeader::conformant( const STHeader& other )
+{
+  bool conforms;
+  conforms = (this->antennaname == other.antennaname
+              && this->equinox == other.equinox
+              && this->obstype == other.obstype
+              && this->fluxunit == other.fluxunit
+              );
+  return conforms;
+}
+
+void STHeader::print() const {
+  MVTime mvt(this->utc);
+  mvt.setFormat(MVTime::YMD);
+  cout << "Observer: " << this->observer << endl
+       << "Project: " << this->project << endl
+       << "Obstype: " << this->obstype << endl
+       << "Antenna: " << this->antennaname << endl
+       << "Ant. Position: " << this->antennaposition << endl
+       << "Equinox: " << this->equinox << endl
+       << "Freq. ref.: " << this->freqref << endl
+       << "Ref. frequency: " << this->reffreq << endl
+       << "Bandwidth: "  << this->bandwidth << endl
+       << "Time (utc): "
+       << mvt
+       << endl;
+  //setprecision(10) << this->utc << endl;
+}
+
+// SDDataDesc
+
+uInt SDDataDesc::addEntry(const String& source, uInt ID,
+			  const MDirection& dir, uInt secID)
+{
+
+// See if already exists
+
+  if (n_ > 0) {
+    for (uInt i=0; i<n_; i++) {
+      if (source==source_[i] && ID==ID_[i]) {
+         return i;
+      }
+    }
+  }
+
+// Not found - add it
+
+  n_ += 1;
+  source_.resize(n_,True);
+  ID_.resize(n_,True);
+  secID_.resize(n_,True);
+  secDir_.resize(n_,True,True);
+//
+  source_[n_-1] = source;
+  ID_[n_-1] = ID;
+  secID_[n_-1] = secID;
+  secDir_[n_-1] = dir;
+//
+  return n_-1;
+}
+
+void SDDataDesc::summary() const
+{
+   if (n_>0) {
+      cerr << "Source    ID" << endl;
+      for (uInt i=0; i<n_; i++) {
+         cout << setw(11) << source_(i) << ID_(i) << endl;
+      }
+   }
+}
+
Index: tags/Release2.1.0b/src/STHeader.h
===================================================================
--- tags/Release2.1.0b/src/STHeader.h	(revision 1196)
+++ tags/Release2.1.0b/src/STHeader.h	(revision 1196)
@@ -0,0 +1,112 @@
+//#---------------------------------------------------------------------------
+//# STHeader.h: A container class for single dish integrations
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id:$
+//#---------------------------------------------------------------------------
+#ifndef STHEADER_H
+#define STHEADER_H
+
+#include <vector>
+
+#include <casa/aips.h>
+#include <casa/BasicSL/String.h>
+#include <casa/Arrays/Vector.h>
+#include <casa/Containers/Block.h>
+#include <measures/Measures/MDirection.h>
+
+namespace casa {
+  template<class T> class Matrix;
+}
+
+namespace asap {
+
+
+struct STHeader {
+
+  bool conformant(const STHeader& other);
+
+  casa::Int nchan;
+  casa::Int npol;
+  casa::Int nif;
+  casa::Int nbeam;
+  casa::String observer;
+  casa::String project;
+  casa::String obstype;
+  casa::String antennaname;
+  casa::Vector<casa::Double> antennaposition;
+  casa::Float equinox;
+  casa::String freqref;
+  casa::Double reffreq;
+  casa::Double bandwidth;
+  casa::Double utc;
+  casa::String fluxunit;
+  casa::String epoch;
+  casa::String poltype;
+  void print() const ;
+};
+
+class SDDataDesc {
+
+public:
+
+  // Constructor
+  SDDataDesc() : n_(0) {;}
+  ~SDDataDesc() {;}
+
+  // Add an entry if source name and Integer ID (can be anything you
+  // like, such as FreqID) are unique.  You can add secondary entries
+  // direction and another integer index which are just stored along
+  // with the the primary entries
+  casa::uInt addEntry(const casa::String& source, casa::uInt ID,
+                      const casa::MDirection& secDir, casa::uInt secID);
+
+  // Number of entries
+  casa::Int length() const { return n_;}
+
+  // Get attributes
+  casa::String source(casa::uInt which) const {return source_[which];}
+  casa::uInt ID(casa::uInt which) const {return ID_[which];}
+  casa::uInt secID(casa::uInt which) const {return secID_[which];}
+  casa::MDirection secDir(casa::uInt which) const {return secDir_[which];}
+
+  // Summary
+  void summary() const;
+
+private:
+  casa::uInt n_;
+  casa::Vector<casa::String> source_;
+  casa::Vector<casa::uInt> ID_, secID_;
+  casa::Block<casa::MDirection> secDir_;
+
+  SDDataDesc(const SDDataDesc& other);
+
+};
+
+
+} // namespace
+#endif
Index: tags/Release2.1.0b/src/STHistory.cpp
===================================================================
--- tags/Release2.1.0b/src/STHistory.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STHistory.cpp	(revision 1196)
@@ -0,0 +1,98 @@
+//
+// C++ Implementation: STHistory
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#include <casa/Exceptions/Error.h>
+#include <tables/Tables/TableDesc.h>
+#include <tables/Tables/SetupNewTab.h>
+#include <tables/Tables/ScaColDesc.h>
+#include <tables/Tables/TableParse.h>
+#include <tables/Tables/TableRow.h>
+#include <tables/Tables/TableCopy.h>
+
+#include "STDefs.h"
+#include "STHistory.h"
+
+
+using namespace casa;
+
+namespace asap {
+
+const casa::String STHistory::name_ = "HISTORY";
+
+STHistory::STHistory(const Scantable& parent ) :
+  STSubTable( parent, name_ )
+{
+  setup();
+}
+
+asap::STHistory::STHistory( casa::Table tab ) : STSubTable(tab, name_)
+{
+  itemCol_.attach(table_,"ITEM");
+}
+
+STHistory::~STHistory()
+{
+}
+
+STHistory& asap::STHistory::operator =( const STHistory & other )
+{
+  if (this != &other) {
+    static_cast<STSubTable&>(*this) = other;
+    itemCol_.attach(table_,"ITEM");
+  }
+  return *this;
+}
+void asap::STHistory::setup( )
+{
+  // add to base class table
+  table_.addColumn(ScalarColumnDesc<String>("ITEM"));
+
+  // new cached columns
+  itemCol_.attach(table_,"ITEM");
+}
+
+uInt STHistory::addEntry( const String& item)
+{
+  uInt rno = table_.nrow();
+  table_.addRow();
+  itemCol_.put(rno, item);
+  idCol_.put(rno, 0);
+  return 0;
+}
+
+void asap::STHistory::getEntry( String& item, uInt id)
+{
+  Table t = table_(table_.col("ID") == Int(id) );
+  if (t.nrow() == 0 ) {
+    throw(AipsError("STHistory::getEntry - id out of range"));
+  }
+  item = "";
+}
+
+void asap::STHistory::append( const STHistory & other )
+{
+  const Table& t = other.table();
+  addEntry(asap::SEPERATOR);
+  TableCopy::copyRows(table_, t, table_.nrow(), 0, t.nrow());
+  addEntry(asap::SEPERATOR);
+
+}
+
+std::vector<std::string> asap::STHistory::getHistory( ) const
+{
+  std::vector<std::string> stlout;
+  for (uInt i=0; i<table_.nrow(); ++i) {
+    stlout.push_back(itemCol_(i));
+  }
+  return stlout;
+}
+
+}
Index: tags/Release2.1.0b/src/STHistory.h
===================================================================
--- tags/Release2.1.0b/src/STHistory.h	(revision 1196)
+++ tags/Release2.1.0b/src/STHistory.h	(revision 1196)
@@ -0,0 +1,76 @@
+//
+// C++ Interface: STHistory
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPSTHISTORY_H
+#define ASAPSTHISTORY_H
+
+#include <casa/aips.h>
+#include <casa/BasicSL/String.h>
+#include <tables/Tables/Table.h>
+#include <tables/Tables/ScalarColumn.h>
+
+#include "STSubTable.h"
+
+namespace asap {
+
+/**
+The History subtable of the Scantable
+
+@author Malte Marquarding
+*/
+class STHistory : public STSubTable {
+public:
+  STHistory() {;}
+  STHistory(casa::Table tab);
+  STHistory( const Scantable& parent );
+
+  virtual ~STHistory();
+
+  STHistory& operator=(const STHistory& other);
+
+  /**
+   * add another row to this table
+   * @param item the histor string
+   * @return an ID (irrelevant)
+   */
+  casa::uInt addEntry( const casa::String& item);
+
+  /**
+   * Just for consistency sake, doesn't do anything
+   * as IDs are irrelevant in this class
+   * @param  item a string
+   * @param id an uInt ID
+   */
+  void getEntry( casa::String& item, casa::uInt id);
+
+  /**
+   * add all rows from another STHistory table
+   * @param other a STHistory reference
+   */
+  void append(const STHistory& other);
+
+  /**
+   * Get all rows in the table as a vector
+   * @return a vector of strings
+   */
+  std::vector<std::string> getHistory( ) const;
+
+  const casa::String& name() const { return name_; }
+
+private:
+  void setup();
+  static const casa::String name_;
+  casa::ScalarColumn<casa::String> itemCol_;
+};
+
+}
+
+#endif
Index: tags/Release2.1.0b/src/STLineFinder.cpp
===================================================================
--- tags/Release2.1.0b/src/STLineFinder.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STLineFinder.cpp	(revision 1196)
@@ -0,0 +1,1106 @@
+//#---------------------------------------------------------------------------
+//# STLineFinder.cc: A class for automated spectral line search
+//#--------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+
+
+// ASAP
+#include "STLineFinder.h"
+#include "STFitter.h"
+
+// STL
+#include <functional>
+#include <algorithm>
+#include <iostream>
+#include <fstream>
+
+using namespace asap;
+using namespace casa;
+using namespace std;
+
+namespace asap {
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// RunningBox -    a running box calculator. This class implements
+//                 interations over the specified spectrum and calculates
+//                 running box filter statistics.
+//
+
+class RunningBox {
+   // The input data to work with. Use reference symantics to avoid
+   // an unnecessary copying
+   const casa::Vector<casa::Float>  &spectrum; // a buffer for the spectrum
+   const casa::Vector<casa::Bool>   &mask; // associated mask
+   const std::pair<int,int>         &edge; // start and stop+1 channels
+                                           // to work with
+
+   // statistics for running box filtering
+   casa::Float sumf;       // sum of fluxes
+   casa::Float sumf2;     // sum of squares of fluxes
+   casa::Float sumch;       // sum of channel numbers (for linear fit)
+   casa::Float sumch2;     // sum of squares of channel numbers (for linear fit)
+   casa::Float sumfch;     // sum of flux*(channel number) (for linear fit)
+
+   int box_chan_cntr;     // actual number of channels in the box
+   int max_box_nchan;     // maximum allowed number of channels in the box
+                          // (calculated from boxsize and actual spectrum size)
+   // cache for derivative statistics
+   mutable casa::Bool need2recalculate; // if true, values of the statistics
+                                       // below are invalid
+   mutable casa::Float linmean;  // a value of the linear fit to the
+                                 // points in the running box
+   mutable casa::Float linvariance; // the same for variance
+   int cur_channel;       // the number of the current channel
+   int start_advance;     // number of channel from which the box can
+                          // be moved (the middle of the box, if there is no
+                          // masking)
+public:
+   // set up the object with the references to actual data
+   // as well as the number of channels in the running box
+   RunningBox(const casa::Vector<casa::Float>  &in_spectrum,
+                 const casa::Vector<casa::Bool>   &in_mask,
+                 const std::pair<int,int>         &in_edge,
+                 int in_max_box_nchan) throw(AipsError);
+
+   // access to the statistics
+   const casa::Float& getLinMean() const throw(AipsError);
+   const casa::Float& getLinVariance() const throw(AipsError);
+   const casa::Float aboveMean() const throw(AipsError);
+   int getChannel() const throw();
+
+   // actual number of channels in the box (max_box_nchan, if no channels
+   // are masked)
+   int getNumberOfBoxPoints() const throw();
+
+   // next channel
+   void next() throw(AipsError);
+
+   // checking whether there are still elements
+   casa::Bool haveMore() const throw();
+
+   // go to start
+   void rewind() throw(AipsError);
+
+protected:
+   // supplementary function to control running mean calculations.
+   // It adds a specified channel to the running mean box and
+   // removes (ch-maxboxnchan+1)'th channel from there
+   // Channels, for which the mask is false or index is beyond the
+   // allowed range, are ignored
+   void advanceRunningBox(int ch) throw(casa::AipsError);
+
+   // calculate derivative statistics. This function is const, because
+   // it updates the cache only
+   void updateDerivativeStatistics() const throw(AipsError);
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// LFAboveThreshold   An algorithm for line detection using running box
+//                    statistics.  Line is detected if it is above the
+//                    specified threshold at the specified number of
+//                    consequtive channels. Prefix LF stands for Line Finder
+//
+class LFAboveThreshold : protected LFLineListOperations {
+   // temporary line edge channels and flag, which is True if the line
+   // was detected in the previous channels.
+   std::pair<int,int> cur_line;
+   casa::Bool is_detected_before;
+   int  min_nchan;                         // A minimum number of consequtive
+                                           // channels, which should satisfy
+                                           // the detection criterion, to be
+                                           // a detection
+   casa::Float threshold;                  // detection threshold - the
+                                           // minimal signal to noise ratio
+   std::list<pair<int,int> > &lines;       // list where detections are saved
+                                           // (pair: start and stop+1 channel)
+   RunningBox *running_box;                // running box filter
+   casa::Vector<Int> signs;                // An array to store the signs of
+                                           // the value - current mean
+                                           // (used to search wings)
+   casa::Int last_sign;                    // a sign (+1, -1 or 0) of the
+                                           // last point of the detected line
+                                           //
+public:
+
+   // set up the detection criterion
+   LFAboveThreshold(std::list<pair<int,int> > &in_lines,
+                    int in_min_nchan = 3,
+                    casa::Float in_threshold = 5) throw();
+   virtual ~LFAboveThreshold() throw();
+
+   // replace the detection criterion
+   void setCriterion(int in_min_nchan, casa::Float in_threshold) throw();
+
+   // return the array with signs of the value-current mean
+   // An element is +1 if value>mean, -1 if less, 0 if equal.
+   // This array is updated each time the findLines method is called and
+   // is used to search the line wings
+   const casa::Vector<Int>& getSigns() const throw();
+
+   // find spectral lines and add them into list
+   // if statholder is not NULL, the accumulate function of it will be
+   // called for each channel to save statistics
+   //    spectrum, mask and edge - reference to the data
+   //    max_box_nchan  - number of channels in the running box
+   void findLines(const casa::Vector<casa::Float> &spectrum,
+                  const casa::Vector<casa::Bool> &mask,
+                  const std::pair<int,int> &edge,
+                  int max_box_nchan) throw(casa::AipsError);
+
+protected:
+
+   // process a channel: update curline and is_detected before and
+   // add a new line to the list, if necessary using processCurLine()
+   // detect=true indicates that the current channel satisfies the criterion
+   void processChannel(Bool detect, const casa::Vector<casa::Bool> &mask)
+                                                  throw(casa::AipsError);
+
+   // process the interval of channels stored in curline
+   // if it satisfies the criterion, add this interval as a new line
+   void processCurLine(const casa::Vector<casa::Bool> &mask)
+                                                 throw(casa::AipsError);
+
+   // get the sign of runningBox->aboveMean(). The RunningBox pointer
+   // should be defined
+   casa::Int getAboveMeanSign() const throw();
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace asap
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// RunningBox -    a running box calculator. This class implements
+//                 interations over the specified spectrum and calculates
+//                 running box filter statistics.
+//
+
+// set up the object with the references to actual data
+// and the number of channels in the running box
+RunningBox::RunningBox(const casa::Vector<casa::Float>  &in_spectrum,
+                       const casa::Vector<casa::Bool>   &in_mask,
+                       const std::pair<int,int>         &in_edge,
+                       int in_max_box_nchan) throw(AipsError) :
+        spectrum(in_spectrum), mask(in_mask), edge(in_edge),
+        max_box_nchan(in_max_box_nchan)
+{
+  rewind();
+}
+
+void RunningBox::rewind() throw(AipsError) {
+  // fill statistics for initial box
+  box_chan_cntr=0; // no channels are currently in the box
+  sumf=0.;           // initialize statistics
+  sumf2=0.;
+  sumch=0.;
+  sumch2=0.;
+  sumfch=0.;
+  int initial_box_ch=edge.first;
+  for (;initial_box_ch<edge.second && box_chan_cntr<max_box_nchan;
+        ++initial_box_ch)
+       advanceRunningBox(initial_box_ch);
+
+  if (initial_box_ch==edge.second)
+      throw AipsError("RunningBox::rewind - too much channels are masked");
+
+  cur_channel=edge.first;
+  start_advance=initial_box_ch-max_box_nchan/2;
+}
+
+// access to the statistics
+const casa::Float& RunningBox::getLinMean() const throw(AipsError)
+{
+  DebugAssert(cur_channel<edge.second, AipsError);
+  if (need2recalculate) updateDerivativeStatistics();
+  return linmean;
+}
+
+const casa::Float& RunningBox::getLinVariance() const throw(AipsError)
+{
+  DebugAssert(cur_channel<edge.second, AipsError);
+  if (need2recalculate) updateDerivativeStatistics();
+  return linvariance;
+}
+
+const casa::Float RunningBox::aboveMean() const throw(AipsError)
+{
+  DebugAssert(cur_channel<edge.second, AipsError);
+  if (need2recalculate) updateDerivativeStatistics();
+  return spectrum[cur_channel]-linmean;
+}
+
+int RunningBox::getChannel() const throw()
+{
+  return cur_channel;
+}
+
+// actual number of channels in the box (max_box_nchan, if no channels
+// are masked)
+int RunningBox::getNumberOfBoxPoints() const throw()
+{
+  return box_chan_cntr;
+}
+
+// supplementary function to control running mean calculations.
+// It adds a specified channel to the running mean box and
+// removes (ch-max_box_nchan+1)'th channel from there
+// Channels, for which the mask is false or index is beyond the
+// allowed range, are ignored
+void RunningBox::advanceRunningBox(int ch) throw(AipsError)
+{
+  if (ch>=edge.first && ch<edge.second)
+      if (mask[ch]) { // ch is a valid channel
+          ++box_chan_cntr;
+          sumf+=spectrum[ch];
+          sumf2+=square(spectrum[ch]);
+          sumch+=Float(ch);
+          sumch2+=square(Float(ch));
+          sumfch+=spectrum[ch]*Float(ch);
+          need2recalculate=True;
+      }
+  int ch2remove=ch-max_box_nchan;
+  if (ch2remove>=edge.first && ch2remove<edge.second)
+      if (mask[ch2remove]) { // ch2remove is a valid channel
+          --box_chan_cntr;
+          sumf-=spectrum[ch2remove];
+          sumf2-=square(spectrum[ch2remove]);
+          sumch-=Float(ch2remove);
+          sumch2-=square(Float(ch2remove));
+          sumfch-=spectrum[ch2remove]*Float(ch2remove);
+          need2recalculate=True;
+      }
+}
+
+// next channel
+void RunningBox::next() throw(AipsError)
+{
+   AlwaysAssert(cur_channel<edge.second,AipsError);
+   ++cur_channel;
+   if (cur_channel+max_box_nchan/2<edge.second && cur_channel>=start_advance)
+       advanceRunningBox(cur_channel+max_box_nchan/2); // update statistics
+}
+
+// checking whether there are still elements
+casa::Bool RunningBox::haveMore() const throw()
+{
+   return cur_channel<edge.second;
+}
+
+// calculate derivative statistics. This function is const, because
+// it updates the cache only
+void RunningBox::updateDerivativeStatistics() const throw(AipsError)
+{
+  AlwaysAssert(box_chan_cntr, AipsError);
+
+  Float mean=sumf/Float(box_chan_cntr);
+
+  // linear LSF formulae
+  Float meanch=sumch/Float(box_chan_cntr);
+  Float meanch2=sumch2/Float(box_chan_cntr);
+  if (meanch==meanch2 || box_chan_cntr<3) {
+      // vertical line in the spectrum, can't calculate linmean and linvariance
+      linmean=0.;
+      linvariance=0.;
+  } else {
+      Float coeff=(sumfch/Float(box_chan_cntr)-meanch*mean)/
+                (meanch2-square(meanch));
+      linmean=coeff*(Float(cur_channel)-meanch)+mean;
+      linvariance=sqrt(sumf2/Float(box_chan_cntr)-square(mean)-
+                    square(coeff)*(meanch2-square(meanch)));
+  }
+  need2recalculate=False;
+}
+
+
+//
+///////////////////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// LFAboveThreshold - a running mean algorithm for line detection
+//
+//
+
+
+// set up the detection criterion
+LFAboveThreshold::LFAboveThreshold(std::list<pair<int,int> > &in_lines,
+                                   int in_min_nchan,
+                                   casa::Float in_threshold) throw() :
+             min_nchan(in_min_nchan), threshold(in_threshold),
+             lines(in_lines), running_box(NULL) {}
+
+LFAboveThreshold::~LFAboveThreshold() throw()
+{
+  if (running_box!=NULL) delete running_box;
+}
+
+// replace the detection criterion
+void LFAboveThreshold::setCriterion(int in_min_nchan, casa::Float in_threshold)
+                                 throw()
+{
+  min_nchan=in_min_nchan;
+  threshold=in_threshold;
+}
+
+// get the sign of runningBox->aboveMean(). The RunningBox pointer
+// should be defined
+casa::Int LFAboveThreshold::getAboveMeanSign() const throw()
+{
+  const Float buf=running_box->aboveMean();
+  if (buf>0) return 1;
+  if (buf<0) return -1;
+  return 0;
+}
+
+
+// process a channel: update cur_line and is_detected before and
+// add a new line to the list, if necessary
+void LFAboveThreshold::processChannel(Bool detect,
+                 const casa::Vector<casa::Bool> &mask) throw(casa::AipsError)
+{
+  try {
+       if (is_detected_before) {
+          // we have to check that the current detection has the
+          // same sign of running_box->aboveMean
+          // otherwise it could be a spurious detection
+          if (last_sign && last_sign!=getAboveMeanSign())
+              detect=False;
+          }
+          if (detect) {
+              last_sign=getAboveMeanSign();
+              if (is_detected_before)
+                  cur_line.second=running_box->getChannel()+1;
+              else {
+                  is_detected_before=True;
+                  cur_line.first=running_box->getChannel();
+                  cur_line.second=running_box->getChannel()+1;
+              }
+          } else processCurLine(mask);
+  }
+  catch (const AipsError &ae) {
+      throw;
+  }
+  catch (const exception &ex) {
+      throw AipsError(String("LFAboveThreshold::processChannel - STL error: ")+ex.what());
+  }
+}
+
+// process the interval of channels stored in cur_line
+// if it satisfies the criterion, add this interval as a new line
+void LFAboveThreshold::processCurLine(const casa::Vector<casa::Bool> &mask)
+                                   throw(casa::AipsError)
+{
+  try {
+       if (is_detected_before) {
+           if (cur_line.second-cur_line.first>min_nchan) {
+               // it was a detection. We need to change the list
+               Bool add_new_line=False;
+               if (lines.size()) {
+                   for (int i=lines.back().second;i<cur_line.first;++i)
+                        if (mask[i]) { // one valid channel in between
+                                //  means that we deal with a separate line
+                            add_new_line=True;
+                            break;
+                        }
+               } else add_new_line=True;
+               if (add_new_line)
+                   lines.push_back(cur_line);
+               else lines.back().second=cur_line.second;
+           }
+           is_detected_before=False;
+       }
+  }
+  catch (const AipsError &ae) {
+      throw;
+  }
+  catch (const exception &ex) {
+      throw AipsError(String("LFAboveThreshold::processCurLine - STL error: ")+ex.what());
+  }
+}
+
+// return the array with signs of the value-current mean
+// An element is +1 if value>mean, -1 if less, 0 if equal.
+// This array is updated each time the findLines method is called and
+// is used to search the line wings
+const casa::Vector<Int>& LFAboveThreshold::getSigns() const throw()
+{
+  return signs;
+}
+
+// find spectral lines and add them into list
+void LFAboveThreshold::findLines(const casa::Vector<casa::Float> &spectrum,
+                              const casa::Vector<casa::Bool> &mask,
+                              const std::pair<int,int> &edge,
+                              int max_box_nchan)
+                        throw(casa::AipsError)
+{
+  const int minboxnchan=4;
+  try {
+
+      if (running_box!=NULL) delete running_box;
+      running_box=new RunningBox(spectrum,mask,edge,max_box_nchan);
+
+
+      // determine the off-line variance first
+      // an assumption made: lines occupy a small part of the spectrum
+
+      std::vector<float> variances(edge.second-edge.first);
+      DebugAssert(variances.size(),AipsError);
+
+      for (;running_box->haveMore();running_box->next())
+           variances[running_box->getChannel()-edge.first]=
+                                running_box->getLinVariance();
+
+      // in the future we probably should do a proper Chi^2 estimation
+      // now a simple 80% of smaller values will be used.
+      // it may degrade the performance of the algorithm for weak lines
+      // due to a bias of the Chi^2 distribution.
+      stable_sort(variances.begin(),variances.end());
+
+      Float offline_variance=0;
+      uInt offline_cnt=uInt(0.8*variances.size());
+      if (!offline_cnt) offline_cnt=variances.size(); // no much else left,
+                                    // although it is very inaccurate
+      for (uInt n=0;n<offline_cnt;++n)
+           offline_variance+=variances[n];
+      offline_variance/=Float(offline_cnt);
+
+      // actual search algorithm
+      is_detected_before=False;
+
+      // initiate the signs array
+      signs.resize(spectrum.nelements());
+      signs=Vector<Int>(spectrum.nelements(),0);
+
+      //ofstream os("dbg.dat");
+      for (running_box->rewind();running_box->haveMore();
+                                 running_box->next()) {
+           const int ch=running_box->getChannel();
+           if (running_box->getNumberOfBoxPoints()>=minboxnchan)
+               processChannel(mask[ch] && (fabs(running_box->aboveMean()) >=
+                  threshold*offline_variance), mask);
+           else processCurLine(mask); // just finish what was accumulated before
+
+           signs[ch]=getAboveMeanSign();
+           // os<<ch<<" "<<spectrum[ch]<<" "<<fabs(running_box->aboveMean())<<" "<<
+           //   threshold*offline_variance<<endl;
+
+           const Float buf=running_box->aboveMean();
+           if (buf>0) signs[ch]=1;
+           else if (buf<0) signs[ch]=-1;
+           else if (buf==0) signs[ch]=0;
+        //   os<<ch<<" "<<spectrum[ch]<<" "<<running_box->getLinMean()<<" "<<
+          //              threshold*offline_variance<<endl;
+      }
+      if (lines.size())
+          searchForWings(lines,signs,mask,edge);
+  }
+  catch (const AipsError &ae) {
+      throw;
+  }
+  catch (const exception &ex) {
+      throw AipsError(String("LFAboveThreshold::findLines - STL error: ")+ex.what());
+  }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// LFLineListOperations::IntersectsWith  -  An auxiliary object function
+//                to test whether two lines have a non-void intersection
+//
+
+
+// line1 - range of the first line: start channel and stop+1
+LFLineListOperations::IntersectsWith::IntersectsWith(const std::pair<int,int> &in_line1) :
+                          line1(in_line1) {}
+
+
+// return true if line2 intersects with line1 with at least one
+// common channel, and false otherwise
+// line2 - range of the second line: start channel and stop+1
+bool LFLineListOperations::IntersectsWith::operator()(const std::pair<int,int> &line2)
+                          const throw()
+{
+  if (line2.second<line1.first) return false; // line2 is at lower channels
+  if (line2.first>line1.second) return false; // line2 is at upper channels
+  return true; // line2 has an intersection or is adjacent to line1
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// LFLineListOperations::BuildUnion - An auxiliary object function to build a union
+// of several lines to account for a possibility of merging the nearby lines
+//
+
+// set an initial line (can be a first line in the sequence)
+LFLineListOperations::BuildUnion::BuildUnion(const std::pair<int,int> &line1) :
+                             temp_line(line1) {}
+
+// update temp_line with a union of temp_line and new_line
+// provided there is no gap between the lines
+void LFLineListOperations::BuildUnion::operator()(const std::pair<int,int> &new_line)
+                                   throw()
+{
+  if (new_line.first<temp_line.first) temp_line.first=new_line.first;
+  if (new_line.second>temp_line.second) temp_line.second=new_line.second;
+}
+
+// return the result (temp_line)
+const std::pair<int,int>& LFLineListOperations::BuildUnion::result() const throw()
+{
+  return temp_line;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// LFLineListOperations::LaterThan - An auxiliary object function to test whether a
+// specified line is at lower spectral channels (to preserve the order in
+// the line list)
+//
+
+// setup the line to compare with
+LFLineListOperations::LaterThan::LaterThan(const std::pair<int,int> &in_line1) :
+                         line1(in_line1) {}
+
+// return true if line2 should be placed later than line1
+// in the ordered list (so, it is at greater channel numbers)
+bool LFLineListOperations::LaterThan::operator()(const std::pair<int,int> &line2)
+                          const throw()
+{
+  if (line2.second<line1.first) return false; // line2 is at lower channels
+  if (line2.first>line1.second) return true; // line2 is at upper channels
+
+  // line2 intersects with line1. We should have no such situation in
+  // practice
+  return line2.first>line1.first;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// STLineFinder  -  a class for automated spectral line search
+//
+//
+
+STLineFinder::STLineFinder() throw() : edge(0,0)
+{
+  setOptions();
+}
+
+// set the parameters controlling algorithm
+// in_threshold a single channel threshold default is sqrt(3), which
+//              means together with 3 minimum channels at least 3 sigma
+//              detection criterion
+//              For bad baseline shape, in_threshold may need to be
+//              increased
+// in_min_nchan minimum number of channels above the threshold to report
+//              a detection, default is 3
+// in_avg_limit perform the averaging of no more than in_avg_limit
+//              adjacent channels to search for broad lines
+//              Default is 8, but for a bad baseline shape this
+//              parameter should be decreased (may be even down to a
+//              minimum of 1 to disable this option) to avoid
+//              confusing of baseline undulations with a real line.
+//              Setting a very large value doesn't usually provide
+//              valid detections.
+// in_box_size  the box size for running mean calculation. Default is
+//              1./5. of the whole spectrum size
+void STLineFinder::setOptions(const casa::Float &in_threshold,
+                              const casa::Int &in_min_nchan,
+                              const casa::Int &in_avg_limit,
+                              const casa::Float &in_box_size) throw()
+{
+  threshold=in_threshold;
+  min_nchan=in_min_nchan;
+  avg_limit=in_avg_limit;
+  box_size=in_box_size;
+}
+
+STLineFinder::~STLineFinder() throw(AipsError) {}
+
+// set scan to work with (in_scan parameter)
+void STLineFinder::setScan(const ScantableWrapper &in_scan) throw(AipsError)
+{
+  scan=in_scan.getCP();
+  AlwaysAssert(!scan.null(),AipsError);
+
+}
+
+// search for spectral lines. Number of lines found is returned
+// in_edge and in_mask control channel rejection for a given row
+//   if in_edge has zero length, all channels chosen by mask will be used
+//   if in_edge has one element only, it represents the number of
+//      channels to drop from both sides of the spectrum
+//   in_edge is introduced for convinience, although all functionality
+//   can be achieved using a spectrum mask only
+int STLineFinder::findLines(const std::vector<bool> &in_mask,
+                const std::vector<int> &in_edge,
+                const casa::uInt &whichRow) throw(casa::AipsError)
+{
+  //const int minboxnchan=4;
+  if (scan.null())
+      throw AipsError("STLineFinder::findLines - a scan should be set first,"
+                      " use set_scan");
+
+  uInt nchan = scan->nchan(scan->getIF(whichRow));
+  // set up mask and edge rejection
+  // no mask given...
+  if (in_mask.size() == 0) {
+    mask = Vector<Bool>(nchan,True);
+  } else {
+    // use provided mask
+    mask=Vector<Bool>(in_mask);
+  }
+  if (mask.nelements()!=nchan)
+      throw AipsError("STLineFinder::findLines - in_scan and in_mask have different"
+            "number of spectral channels.");
+  // number of elements in in_edge
+  if (in_edge.size()>2)
+      throw AipsError("STLineFinder::findLines - the length of the in_edge parameter"
+                      "should not exceed 2");
+      if (!in_edge.size()) {
+           // all spectra, no rejection
+           edge.first=0;
+           edge.second=nchan;
+      } else {
+           edge.first=in_edge[0];
+           if (edge.first<0)
+               throw AipsError("STLineFinder::findLines - the in_edge parameter has a negative"
+                                "number of channels to drop");
+           if (edge.first>=int(nchan))
+               throw AipsError("STLineFinder::findLines - all channels are rejected by the in_edge parameter");
+           if (in_edge.size()==2) {
+               edge.second=in_edge[1];
+               if (edge.second<0)
+                   throw AipsError("STLineFinder::findLines - the in_edge parameter has a negative"
+                                   "number of channels to drop");
+               edge.second=nchan-edge.second;
+           } else edge.second=nchan-edge.first;
+           if (edge.second<0 || (edge.first>=edge.second))
+               throw AipsError("STLineFinder::findLines - all channels are rejected by the in_edge parameter");
+       }
+
+  //
+  int max_box_nchan=int(nchan*box_size); // number of channels in running
+                                                 // box
+  if (max_box_nchan<2)
+      throw AipsError("STLineFinder::findLines - box_size is too small");
+
+  spectrum.resize();
+  spectrum = Vector<Float>(scan->getSpectrum(whichRow));
+
+  lines.resize(0); // search from the scratch
+  last_row_used=whichRow;
+  Vector<Bool> temp_mask(mask);
+
+  Bool first_pass=True;
+  Int avg_factor=1; // this number of adjacent channels is averaged together
+                    // the total number of the channels is not altered
+                    // instead, min_nchan is also scaled
+                    // it helps to search for broad lines
+  Vector<Int> signs; // a buffer for signs of the value - mean quantity
+                     // see LFAboveThreshold for details
+                     // We need only signs resulted from last iteration
+                     // because all previous values may be corrupted by the
+                     // presence of spectral lines
+  while (true) {
+     // a buffer for new lines found at this iteration
+     std::list<pair<int,int> > new_lines;
+
+     try {
+         // line find algorithm
+         LFAboveThreshold lfalg(new_lines,avg_factor*min_nchan, threshold);
+         lfalg.findLines(spectrum,temp_mask,edge,max_box_nchan);
+         signs.resize(lfalg.getSigns().nelements());
+         signs=lfalg.getSigns();
+         first_pass=False;
+         if (!new_lines.size())
+              throw AipsError("spurious"); // nothing new - use the same
+                                           // code as for a real exception
+     }
+     catch(const AipsError &ae) {
+         if (first_pass) throw;
+         // nothing new - proceed to the next step of averaging, if any
+         // (to search for broad lines)
+         if (avg_factor>avg_limit) break; // averaging up to avg_limit
+                                          // adjacent channels,
+                                          // stop after that
+         avg_factor*=2; // twice as more averaging
+         subtractBaseline(temp_mask,9);
+         averageAdjacentChannels(temp_mask,avg_factor);
+         continue;
+     }
+     keepStrongestOnly(temp_mask,new_lines,max_box_nchan);
+     // update the list (lines) merging intervals, if necessary
+     addNewSearchResult(new_lines,lines);
+     // get a new mask
+     temp_mask=getMask();
+  }
+
+  // an additional search for wings because in the presence of very strong
+  // lines temporary mean used at each iteration will be higher than
+  // the true mean
+
+  if (lines.size())
+      LFLineListOperations::searchForWings(lines,signs,mask,edge);
+
+  return int(lines.size());
+}
+
+// auxiliary function to fit and subtract a polynomial from the current
+// spectrum. It uses the Fitter class. This action is required before
+// reducing the spectral resolution if the baseline shape is bad
+void STLineFinder::subtractBaseline(const casa::Vector<casa::Bool> &temp_mask,
+                      const casa::Int &order) throw(casa::AipsError)
+{
+  AlwaysAssert(spectrum.nelements(),AipsError);
+  // use the fact that temp_mask excludes channels rejected at the edge
+  Fitter sdf;
+  std::vector<float> absc(spectrum.nelements());
+  for (unsigned int i=0;i<absc.size();++i)
+       absc[i]=float(i)/float(spectrum.nelements());
+  std::vector<float> spec;
+  spectrum.tovector(spec);
+  std::vector<bool> std_mask;
+  temp_mask.tovector(std_mask);
+  sdf.setData(absc,spec,std_mask);
+  sdf.setExpression("poly",order);
+  if (!sdf.fit()) return; // fit failed, use old spectrum
+  spectrum=casa::Vector<casa::Float>(sdf.getResidual());
+}
+
+// auxiliary function to average adjacent channels and update the mask
+// if at least one channel involved in summation is masked, all
+// output channels will be masked. This function works with the
+// spectrum and edge fields of this class, but updates the mask
+// array specified, rather than the field of this class
+// boxsize - a number of adjacent channels to average
+void STLineFinder::averageAdjacentChannels(casa::Vector<casa::Bool> &mask2update,
+                                   const casa::Int &boxsize)
+                            throw(casa::AipsError)
+{
+  DebugAssert(mask2update.nelements()==spectrum.nelements(), AipsError);
+  DebugAssert(boxsize!=0,AipsError);
+
+  for (int n=edge.first;n<edge.second;n+=boxsize) {
+       DebugAssert(n<spectrum.nelements(),AipsError);
+       int nboxch=0; // number of channels currently in the box
+       Float mean=0; // buffer for mean calculations
+       for (int k=n;k<n+boxsize && k<edge.second;++k)
+            if (mask2update[k]) {  // k is a valid channel
+                mean+=spectrum[k];
+                ++nboxch;
+            }
+       if (nboxch<boxsize) // mask these channels
+           for (int k=n;k<n+boxsize && k<edge.second;++k)
+                mask2update[k]=False;
+       else {
+          mean/=Float(boxsize);
+           for (int k=n;k<n+boxsize && k<edge.second;++k)
+                spectrum[k]=mean;
+       }
+  }
+}
+
+
+// get the mask to mask out all lines that have been found (default)
+// if invert=true, only channels belong to lines will be unmasked
+// Note: all channels originally masked by the input mask (in_mask
+//       in setScan) or dropped out by the edge parameter (in_edge
+//       in setScan) are still excluded regardless on the invert option
+std::vector<bool> STLineFinder::getMask(bool invert)
+                                        const throw(casa::AipsError)
+{
+  try {
+       if (scan.null())
+           throw AipsError("STLineFinder::getMask - a scan should be set first,"
+                      " use set_scan followed by find_lines");
+       DebugAssert(mask.nelements()==scan->getChannels(last_row_used), AipsError);
+       /*
+       if (!lines.size())
+           throw AipsError("STLineFinder::getMask - one have to search for "
+                           "lines first, use find_lines");
+       */
+       std::vector<bool> res_mask(mask.nelements());
+       // iterator through lines
+       std::list<std::pair<int,int> >::const_iterator cli=lines.begin();
+       for (int ch=0;ch<int(res_mask.size());++ch)
+            if (ch<edge.first || ch>=edge.second) res_mask[ch]=false;
+            else if (!mask[ch]) res_mask[ch]=false;
+            else {
+                    res_mask[ch]=!invert; // no line by default
+                    if (cli==lines.end()) continue;
+                    if (ch>=cli->first && ch<cli->second)
+                        res_mask[ch]=invert; // this is a line
+                    if (ch>=cli->second)
+                        ++cli; // next line in the list
+                 }
+
+       return res_mask;
+  }
+  catch (const AipsError &ae) {
+      throw;
+  }
+  catch (const exception &ex) {
+      throw AipsError(String("STLineFinder::getMask - STL error: ")+ex.what());
+  }
+}
+
+// get range for all lines found. The same units as used in the scan
+// will be returned (e.g. velocity instead of channels).
+std::vector<double> STLineFinder::getLineRanges()
+                             const throw(casa::AipsError)
+{
+  // convert to required abscissa units
+  std::vector<double> vel=scan->getAbcissa(last_row_used);
+  std::vector<int> ranges=getLineRangesInChannels();
+  std::vector<double> res(ranges.size());
+
+  std::vector<int>::const_iterator cri=ranges.begin();
+  std::vector<double>::iterator outi=res.begin();
+  for (;cri!=ranges.end() && outi!=res.end();++cri,++outi)
+       if (uInt(*cri)>=vel.size())
+          throw AipsError("STLineFinder::getLineRanges - getAbcissa provided less channels than reqired");
+       else *outi=vel[*cri];
+  return res;
+}
+
+// The same as getLineRanges, but channels are always used to specify
+// the range
+std::vector<int> STLineFinder::getLineRangesInChannels()
+                                   const throw(casa::AipsError)
+{
+  try {
+       if (scan.null())
+           throw AipsError("STLineFinder::getLineRangesInChannels - a scan should be set first,"
+                      " use set_scan followed by find_lines");
+       DebugAssert(mask.nelements()==scan->getChannels(last_row_used), AipsError);
+
+       if (!lines.size())
+           throw AipsError("STLineFinder::getLineRangesInChannels - one have to search for "
+                           "lines first, use find_lines");
+
+       std::vector<int> res(2*lines.size());
+       // iterator through lines & result
+       std::list<std::pair<int,int> >::const_iterator cli=lines.begin();
+       std::vector<int>::iterator ri=res.begin();
+       for (;cli!=lines.end() && ri!=res.end();++cli,++ri) {
+            *ri=cli->first;
+            if (++ri!=res.end())
+                *ri=cli->second-1;
+       }
+       return res;
+  }
+  catch (const AipsError &ae) {
+      throw;
+  }
+  catch (const exception &ex) {
+      throw AipsError(String("STLineFinder::getLineRanges - STL error: ")+ex.what());
+  }
+}
+
+
+
+// an auxiliary function to remove all lines from the list, except the
+// strongest one (by absolute value). If the lines removed are real,
+// they will be find again at the next iteration. This approach
+// increases the number of iterations required, but is able to remove
+// the sidelobes likely to occur near strong lines.
+// Later a better criterion may be implemented, e.g.
+// taking into consideration the brightness of different lines. Now
+// use the simplest solution
+// temp_mask - mask to work with (may be different from original mask as
+// the lines previously found may be masked)
+// lines2update - a list of lines to work with
+//                 nothing will be done if it is empty
+// max_box_nchan - channels in the running box for baseline filtering
+void STLineFinder::keepStrongestOnly(const casa::Vector<casa::Bool> &temp_mask,
+                  std::list<std::pair<int, int> > &lines2update,
+                  int max_box_nchan)
+                                   throw (casa::AipsError)
+{
+  try {
+      if (!lines2update.size()) return; // ignore an empty list
+
+      // current line
+      std::list<std::pair<int,int> >::iterator li=lines2update.begin();
+      // strongest line
+      std::list<std::pair<int,int> >::iterator strongli=lines2update.begin();
+      // the flux (absolute value) of the strongest line
+      Float peak_flux=-1; // negative value - a flag showing uninitialized
+                          // value
+      // the algorithm below relies on the list being ordered
+      Float tmp_flux=-1; // a temporary peak
+      for (RunningBox running_box(spectrum,temp_mask,edge,max_box_nchan);
+           running_box.haveMore(); running_box.next()) {
+
+           if (li==lines2update.end()) break; // no more lines
+           const int ch=running_box.getChannel();
+           if (ch>=li->first && ch<li->second)
+               if (temp_mask[ch] && tmp_flux<fabs(running_box.aboveMean()))
+                   tmp_flux=fabs(running_box.aboveMean());
+           if (ch==li->second-1) {
+               if (peak_flux<tmp_flux) { // if peak_flux=-1, this condition
+                   peak_flux=tmp_flux;   // will be satisfied
+                   strongli=li;
+               }
+               ++li;
+               tmp_flux=-1;
+           }
+      }
+      std::list<std::pair<int,int> > res;
+      res.splice(res.end(),lines2update,strongli);
+      lines2update.clear();
+      lines2update.splice(lines2update.end(),res);
+  }
+  catch (const AipsError &ae) {
+      throw;
+  }
+  catch (const exception &ex) {
+      throw AipsError(String("STLineFinder::keepStrongestOnly - STL error: ")+ex.what());
+  }
+
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// LFLineListOperations - a class incapsulating  operations with line lists
+//                        The LF prefix stands for Line Finder
+//
+
+// concatenate two lists preserving the order. If two lines appear to
+// be adjacent, they are joined into the new one
+void LFLineListOperations::addNewSearchResult(const std::list<pair<int, int> > &newlines,
+                         std::list<std::pair<int, int> > &lines_list)
+                        throw(AipsError)
+{
+  try {
+      for (std::list<pair<int,int> >::const_iterator cli=newlines.begin();
+           cli!=newlines.end();++cli) {
+
+           // the first item, which has a non-void intersection or touches
+           // the new line
+           std::list<pair<int,int> >::iterator pos_beg=find_if(lines_list.begin(),
+                          lines_list.end(), IntersectsWith(*cli));
+           // the last such item
+           std::list<pair<int,int> >::iterator pos_end=find_if(pos_beg,
+                          lines_list.end(), not1(IntersectsWith(*cli)));
+
+           // extract all lines which intersect or touch a new one into
+           // a temporary buffer. This may invalidate the iterators
+           // line_buffer may be empty, if no lines intersects with a new
+           // one.
+           std::list<pair<int,int> > lines_buffer;
+           lines_buffer.splice(lines_buffer.end(),lines_list, pos_beg, pos_end);
+
+           // build a union of all intersecting lines
+           pair<int,int> union_line=for_each(lines_buffer.begin(),
+                   lines_buffer.end(),BuildUnion(*cli)).result();
+
+           // search for a right place for the new line (union_line) and add
+           std::list<pair<int,int> >::iterator pos2insert=find_if(lines_list.begin(),
+                          lines_list.end(), LaterThan(union_line));
+           lines_list.insert(pos2insert,union_line);
+      }
+  }
+  catch (const AipsError &ae) {
+      throw;
+  }
+  catch (const exception &ex) {
+      throw AipsError(String("LFLineListOperations::addNewSearchResult - STL error: ")+ex.what());
+  }
+}
+
+// extend all line ranges to the point where a value stored in the
+// specified vector changes (e.g. value-mean change its sign)
+// This operation is necessary to include line wings, which are below
+// the detection threshold. If lines becomes adjacent, they are
+// merged together. Any masked channel stops the extension
+void LFLineListOperations::searchForWings(std::list<std::pair<int, int> > &newlines,
+           const casa::Vector<casa::Int> &signs,
+           const casa::Vector<casa::Bool> &mask,
+           const std::pair<int,int> &edge) throw(casa::AipsError)
+{
+  try {
+      for (std::list<pair<int,int> >::iterator li=newlines.begin();
+           li!=newlines.end();++li) {
+           // update the left hand side
+           for (int n=li->first-1;n>=edge.first;--n) {
+                if (!mask[n]) break;
+                if (signs[n]==signs[li->first] && signs[li->first])
+                    li->first=n;
+                else break;
+           }
+           // update the right hand side
+           for (int n=li->second;n<edge.second;++n) {
+                if (!mask[n]) break;
+                if (signs[n]==signs[li->second-1] && signs[li->second-1])
+                    li->second=n;
+                else break;
+           }
+      }
+      // need to search for possible mergers.
+      std::list<std::pair<int, int> >  result_buffer;
+      addNewSearchResult(newlines,result_buffer);
+      newlines.clear();
+      newlines.splice(newlines.end(),result_buffer);
+  }
+  catch (const AipsError &ae) {
+      throw;
+  }
+  catch (const exception &ex) {
+      throw AipsError(String("LFLineListOperations::extendLines - STL error: ")+ex.what());
+  }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
Index: tags/Release2.1.0b/src/STLineFinder.h
===================================================================
--- tags/Release2.1.0b/src/STLineFinder.h	(revision 1196)
+++ tags/Release2.1.0b/src/STLineFinder.h	(revision 1196)
@@ -0,0 +1,249 @@
+//#---------------------------------------------------------------------------
+//# STLineFinder.h: A class for automated spectral line search
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+#ifndef STLINEFINDER_H
+#define STLINEFINDER_H
+
+// STL
+#include <vector>
+#include <list>
+#include <utility>
+#include <exception>
+
+// AIPS++
+#include <casa/aips.h>
+#include <casa/Exceptions/Error.h>
+#include <casa/Arrays/Vector.h>
+#include <casa/Utilities/Assert.h>
+#include <casa/Utilities/CountedPtr.h>
+
+// ASAP
+#include "ScantableWrapper.h"
+#include "Scantable.h"
+
+namespace asap {
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// LFLineListOperations - a class incapsulating  operations with line lists
+//                        The LF prefix stands for Line Finder
+//
+
+struct LFLineListOperations {
+   // concatenate two lists preserving the order. If two lines appear to
+   // be adjacent or have a non-void intersection, they are joined into
+   // the new line
+   static void addNewSearchResult(const std::list<std::pair<int, int> >
+                  &newlines, std::list<std::pair<int, int> > &lines_list)
+                           throw(casa::AipsError);
+
+   // extend all line ranges to the point where a value stored in the
+   // specified vector changes (e.g. value-mean change its sign)
+   // This operation is necessary to include line wings, which are below
+   // the detection threshold. If lines becomes adjacent, they are
+   // merged together. Any masked channel stops the extension
+   static void searchForWings(std::list<std::pair<int, int> > &newlines,
+                       const casa::Vector<casa::Int> &signs,
+		       const casa::Vector<casa::Bool> &mask,
+		       const std::pair<int,int> &edge)
+			   throw(casa::AipsError);
+protected:
+
+   // An auxiliary object function to test whether two lines have a non-void
+   // intersection
+   class IntersectsWith : public std::unary_function<pair<int,int>, bool> {
+       std::pair<int,int> line1;           // range of the first line
+                                           // start channel and stop+1
+   public:
+        IntersectsWith(const std::pair<int,int> &in_line1);
+	// return true if line2 intersects with line1 with at least one
+	// common channel, and false otherwise
+	bool operator()(const std::pair<int,int> &line2) const throw();
+   };
+
+   // An auxiliary object function to build a union of several lines
+   // to account for a possibility of merging the nearby lines
+   class BuildUnion {
+       std::pair<int,int> temp_line;       // range of the first line
+                                           // start channel and stop+1
+   public:
+        BuildUnion(const std::pair<int,int> &line1);
+        // update temp_line with a union of temp_line and new_line
+	// provided there is no gap between the lines
+	void operator()(const std::pair<int,int> &new_line) throw();
+	// return the result (temp_line)
+	const std::pair<int,int>& result() const throw();
+   };
+
+   // An auxiliary object function to test whether a specified line
+   // is at lower spectral channels (to preserve the order in the line list)
+   class LaterThan : public std::unary_function<pair<int,int>, bool> {
+       std::pair<int,int> line1;           // range of the first line
+                                           // start channel and stop+1
+   public:
+        LaterThan(const std::pair<int,int> &in_line1);
+
+	// return true if line2 should be placed later than line1
+	// in the ordered list (so, it is at greater channel numbers)
+	bool operator()(const std::pair<int,int> &line2) const throw();
+   };
+
+
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// STLineFinder  -  a class for automated spectral line search
+//
+//
+
+struct STLineFinder : protected LFLineListOperations {
+   STLineFinder() throw();
+   virtual ~STLineFinder() throw(casa::AipsError);
+
+   // set the parameters controlling algorithm
+   // in_threshold a single channel threshold default is sqrt(3), which
+   //              means together with 3 minimum channels at least 3 sigma
+   //              detection criterion
+   //              For bad baseline shape, in_threshold may need to be
+   //              increased
+   // in_min_nchan minimum number of channels above the threshold to report
+   //              a detection, default is 3
+   // in_avg_limit perform the averaging of no more than in_avg_limit
+   //              adjacent channels to search for broad lines
+   //              Default is 8, but for a bad baseline shape this
+   //              parameter should be decreased (may be even down to a
+   //              minimum of 1 to disable this option) to avoid
+   //              confusing of baseline undulations with a real line.
+   //              Setting a very large value doesn't usually provide
+   //              valid detections.
+   // in_box_size  the box size for running mean calculation. Default is
+   //              1./5. of the whole spectrum size
+   void setOptions(const casa::Float &in_threshold=sqrt(3.),
+                   const casa::Int &in_min_nchan=3,
+		   const casa::Int &in_avg_limit=8,
+                   const casa::Float &in_box_size=0.2) throw();
+
+   // set the scan to work with (in_scan parameter)
+   void setScan(const ScantableWrapper &in_scan) throw(casa::AipsError);
+
+   // search for spectral lines in a row specified by whichRow 
+   // in_mask and in_edge parameters control channel rejection
+   //  if in_edge has zero length, all channels chosen by mask will be used
+   //   if in_edge has one element only, it represents the number of
+   //      channels to drop from both sides of the spectrum
+   //   in_edge is introduced for convinience, although all functionality
+   //   can be achieved using a spectrum mask only
+   // Number of lines found is returned
+   int findLines(const std::vector<bool> &in_mask,
+		 const std::vector<int> &in_edge = std::vector<int>(),
+		 const casa::uInt &whichRow = 0) throw(casa::AipsError);
+
+   // get the mask to mask out all lines that have been found (default)
+   // if invert=true, only channels belong to lines will be unmasked
+   // Note: all channels originally masked by the input mask (in_mask
+   //       in setScan) or dropped out by the edge parameter (in_edge
+   //       in setScan) are still excluded regardless on the invert option
+   std::vector<bool> getMask(bool invert=false) const throw(casa::AipsError);
+
+   // get range for all lines found. The same units as used in the scan
+   // will be returned (e.g. velocity instead of channels).
+   std::vector<double>   getLineRanges() const throw(casa::AipsError);
+   // The same as getLineRanges, but channels are always used to specify
+   // the range
+   std::vector<int> getLineRangesInChannels() const throw(casa::AipsError);
+protected:
+   // auxiliary function to average adjacent channels and update the mask
+   // if at least one channel involved in summation is masked, all
+   // output channels will be masked. This function works with the
+   // spectrum and edge fields of this class, but updates the mask
+   // array specified, rather than the field of this class
+   // boxsize - a number of adjacent channels to average
+   void averageAdjacentChannels(casa::Vector<casa::Bool> &mask2update,
+                               const casa::Int &boxsize)
+                               throw(casa::AipsError);
+
+   // auxiliary function to fit and subtract a polynomial from the current
+   // spectrum. It uses the Fitter class. This action is required before
+   // reducing the spectral resolution if the baseline shape is bad
+   void subtractBaseline(const casa::Vector<casa::Bool> &temp_mask,
+                         const casa::Int &order) throw(casa::AipsError);
+
+   // an auxiliary function to remove all lines from the list, except the
+   // strongest one (by absolute value). If the lines removed are real,
+   // they will be find again at the next iteration. This approach
+   // increases the number of iterations required, but is able to remove
+   // the sidelobes likely to occur near strong lines.
+   // Later a better criterion may be implemented, e.g.
+   // taking into consideration the brightness of different lines. Now
+   // use the simplest solution
+   // temp_mask - mask to work with (may be different from original mask as
+   // the lines previously found may be masked)
+   // lines2update - a list of lines to work with
+   //                 nothing will be done if it is empty
+   // max_box_nchan - channels in the running box for baseline filtering
+   void keepStrongestOnly(const casa::Vector<casa::Bool> &temp_mask,
+			  std::list<std::pair<int, int> > &lines2update,
+			  int max_box_nchan)
+                                      throw (casa::AipsError);
+private:
+   casa::CountedConstPtr<Scantable> scan; // the scan to work with
+   casa::Vector<casa::Bool> mask;          // associated mask
+   std::pair<int,int> edge;                // start and stop+1 channels
+                                           // to work with
+   casa::Float threshold;                  // detection threshold - the
+                                           // minimal signal to noise ratio
+   casa::Double box_size;	           // size of the box for running
+                                           // mean calculations, specified as
+					   // a fraction of the whole spectrum
+   int  min_nchan;                         // A minimum number of consequtive
+                                           // channels, which should satisfy
+					   // the detection criterion, to be
+					   // a detection
+   casa::Int   avg_limit;                  // perform the averaging of no
+                                           // more than in_avg_limit
+					   // adjacent channels to search
+					   // for broad lines. see setOptions
+   casa::uInt last_row_used;                // the Row number specified
+                                           // during the last findLines call
+   std::list<std::pair<int, int> > lines;  // container of start and stop+1
+                                           // channels of the spectral lines
+   // a buffer for the spectrum
+   mutable casa::Vector<casa::Float>  spectrum;
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace asap
+#endif // #ifndef STLINEFINDER_H
Index: tags/Release2.1.0b/src/STMath.cpp
===================================================================
--- tags/Release2.1.0b/src/STMath.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STMath.cpp	(revision 1196)
@@ -0,0 +1,1424 @@
+//
+// C++ Implementation: STMath
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+
+#include <casa/iomanip.h>
+#include <casa/Exceptions/Error.h>
+#include <casa/Containers/Block.h>
+#include <casa/BasicSL/String.h>
+#include <casa/Arrays/MaskArrLogi.h>
+#include <casa/Arrays/MaskArrMath.h>
+#include <casa/Arrays/ArrayLogical.h>
+#include <casa/Arrays/ArrayMath.h>
+#include <casa/Arrays/Slice.h>
+#include <casa/Arrays/Slicer.h>
+#include <casa/Containers/RecordField.h>
+#include <tables/Tables/TableRow.h>
+#include <tables/Tables/TableVector.h>
+#include <tables/Tables/TabVecMath.h>
+#include <tables/Tables/ExprNode.h>
+#include <tables/Tables/TableRecord.h>
+#include <tables/Tables/TableParse.h>
+#include <tables/Tables/ReadAsciiTable.h>
+#include <tables/Tables/TableIter.h>
+#include <tables/Tables/TableCopy.h>
+#include <scimath/Mathematics/FFTServer.h>
+
+#include <lattices/Lattices/LatticeUtilities.h>
+
+#include <coordinates/Coordinates/SpectralCoordinate.h>
+#include <coordinates/Coordinates/CoordinateSystem.h>
+#include <coordinates/Coordinates/CoordinateUtil.h>
+#include <coordinates/Coordinates/FrequencyAligner.h>
+
+#include <scimath/Mathematics/VectorKernel.h>
+#include <scimath/Mathematics/Convolver.h>
+#include <scimath/Functionals/Polynomial.h>
+
+#include "MathUtils.h"
+#include "RowAccumulator.h"
+#include "STAttr.h"
+#include "STMath.h"
+
+using namespace casa;
+
+using namespace asap;
+
+STMath::STMath(bool insitu) :
+  insitu_(insitu)
+{
+}
+
+
+STMath::~STMath()
+{
+}
+
+CountedPtr<Scantable>
+STMath::average( const std::vector<CountedPtr<Scantable> >& in,
+                 const std::vector<bool>& mask,
+                 const std::string& weight,
+                 const std::string& avmode)
+{
+  if ( avmode == "SCAN" && in.size() != 1 )
+    throw(AipsError("Can't perform 'SCAN' averaging on multiple tables.\n"
+                    "Use merge first."));
+  WeightType wtype = stringToWeight(weight);
+
+  // output
+  // clone as this is non insitu
+  bool insitu = insitu_;
+  setInsitu(false);
+  CountedPtr< Scantable > out = getScantable(in[0], true);
+  setInsitu(insitu);
+  std::vector<CountedPtr<Scantable> >::const_iterator stit = in.begin();
+  ++stit;
+  while ( stit != in.end() ) {
+    out->appendToHistoryTable((*stit)->history());
+    ++stit;
+  }
+
+  Table& tout = out->table();
+
+  /// @todo check if all scantables are conformant
+
+  ArrayColumn<Float> specColOut(tout,"SPECTRA");
+  ArrayColumn<uChar> flagColOut(tout,"FLAGTRA");
+  ArrayColumn<Float> tsysColOut(tout,"TSYS");
+  ScalarColumn<Double> mjdColOut(tout,"TIME");
+  ScalarColumn<Double> intColOut(tout,"INTERVAL");
+  ScalarColumn<uInt> cycColOut(tout,"CYCLENO");
+  ScalarColumn<uInt> scanColOut(tout,"SCANNO");
+
+  // set up the output table rows. These are based on the structure of the
+  // FIRST scantable in the vector
+  const Table& baset = in[0]->table();
+
+  Block<String> cols(3);
+  cols[0] = String("BEAMNO");
+  cols[1] = String("IFNO");
+  cols[2] = String("POLNO");
+  if ( avmode == "SOURCE" ) {
+    cols.resize(4);
+    cols[3] = String("SRCNAME");
+  }
+  if ( avmode == "SCAN"  && in.size() == 1) {
+    cols.resize(4);
+    cols[3] = String("SCANNO");
+  }
+  uInt outrowCount = 0;
+  TableIterator iter(baset, cols);
+  while (!iter.pastEnd()) {
+    Table subt = iter.table();
+    // copy the first row of this selection into the new table
+    tout.addRow();
+    TableCopy::copyRows(tout, subt, outrowCount, 0, 1);
+    // re-index to 0
+    if ( avmode != "SCAN" && avmode != "SOURCE" ) {
+      scanColOut.put(outrowCount, uInt(0));
+    }
+    ++outrowCount;
+    ++iter;
+  }
+  RowAccumulator acc(wtype);
+  Vector<Bool> cmask(mask);
+  acc.setUserMask(cmask);
+  ROTableRow row(tout);
+  ROArrayColumn<Float> specCol, tsysCol;
+  ROArrayColumn<uChar> flagCol;
+  ROScalarColumn<Double> mjdCol, intCol;
+  ROScalarColumn<Int> scanIDCol;
+
+  for (uInt i=0; i < tout.nrow(); ++i) {
+    for ( int j=0; j < int(in.size()); ++j ) {
+      const Table& tin = in[j]->table();
+      const TableRecord& rec = row.get(i);
+      ROScalarColumn<Double> tmp(tin, "TIME");
+      Double td;tmp.get(0,td);
+      Table basesubt = tin(tin.col("BEAMNO") == Int(rec.asuInt("BEAMNO"))
+                       && tin.col("IFNO") == Int(rec.asuInt("IFNO"))
+                       && tin.col("POLNO") == Int(rec.asuInt("POLNO")) );
+      Table subt;
+      if ( avmode == "SOURCE") {
+        subt = basesubt( basesubt.col("SRCNAME") == rec.asString("SRCNAME") );
+      } else if (avmode == "SCAN") {
+        subt = basesubt( basesubt.col("SCANNO") == Int(rec.asuInt("SCANNO")) );
+      } else {
+        subt = basesubt;
+      }
+      specCol.attach(subt,"SPECTRA");
+      flagCol.attach(subt,"FLAGTRA");
+      tsysCol.attach(subt,"TSYS");
+      intCol.attach(subt,"INTERVAL");
+      mjdCol.attach(subt,"TIME");
+      Vector<Float> spec,tsys;
+      Vector<uChar> flag;
+      Double inter,time;
+      for (uInt k = 0; k < subt.nrow(); ++k ) {
+        flagCol.get(k, flag);
+        Vector<Bool> bflag(flag.shape());
+        convertArray(bflag, flag);
+        if ( allEQ(bflag, True) ) {
+          continue;//don't accumulate
+        }
+        specCol.get(k, spec);
+        tsysCol.get(k, tsys);
+        intCol.get(k, inter);
+        mjdCol.get(k, time);
+        // spectrum has to be added last to enable weighting by the other values
+        acc.add(spec, !bflag, tsys, inter, time);
+      }
+    }
+    //write out
+    specColOut.put(i, acc.getSpectrum());
+    const Vector<Bool>& msk = acc.getMask();
+    Vector<uChar> flg(msk.shape());
+    convertArray(flg, !msk);
+    flagColOut.put(i, flg);
+    tsysColOut.put(i, acc.getTsys());
+    intColOut.put(i, acc.getInterval());
+    mjdColOut.put(i, acc.getTime());
+    // we should only have one cycle now -> reset it to be 0
+    // frequency switched data has different CYCLENO for different IFNO
+    // which requires resetting this value
+    cycColOut.put(i, uInt(0));
+    acc.reset();
+  }
+  return out;
+}
+
+CountedPtr< Scantable >
+  STMath::averageChannel( const CountedPtr < Scantable > & in,
+                          const std::string & mode,
+                          const std::string& avmode )
+{
+  // clone as this is non insitu
+  bool insitu = insitu_;
+  setInsitu(false);
+  CountedPtr< Scantable > out = getScantable(in, true);
+  setInsitu(insitu);
+  Table& tout = out->table();
+  ArrayColumn<Float> specColOut(tout,"SPECTRA");
+  ArrayColumn<uChar> flagColOut(tout,"FLAGTRA");
+  ArrayColumn<Float> tsysColOut(tout,"TSYS");
+  ScalarColumn<uInt> scanColOut(tout,"SCANNO");
+  Table tmp = in->table().sort("BEAMNO");
+  Block<String> cols(3);
+  cols[0] = String("BEAMNO");
+  cols[1] = String("IFNO");
+  cols[2] = String("POLNO");
+  if ( avmode == "SCAN") {
+    cols.resize(4);
+    cols[3] = String("SCANNO");
+  }
+  uInt outrowCount = 0;
+  uChar userflag = 1 << 7;
+  TableIterator iter(tmp, cols);
+  while (!iter.pastEnd()) {
+    Table subt = iter.table();
+    ROArrayColumn<Float> specCol, tsysCol;
+    ROArrayColumn<uChar> flagCol;
+    specCol.attach(subt,"SPECTRA");
+    flagCol.attach(subt,"FLAGTRA");
+    tsysCol.attach(subt,"TSYS");
+    tout.addRow();
+    TableCopy::copyRows(tout, subt, outrowCount, 0, 1);
+    if ( avmode != "SCAN") {
+      scanColOut.put(outrowCount, uInt(0));
+    }
+    Vector<Float> tmp;
+    specCol.get(0, tmp);
+    uInt nchan = tmp.nelements();
+    // have to do channel by channel here as MaskedArrMath
+    // doesn't have partialMedians
+    Vector<uChar> flags = flagCol.getColumn(Slicer(Slice(0)));
+    Vector<Float> outspec(nchan);
+    Vector<uChar> outflag(nchan,0);
+    Vector<Float> outtsys(1);/// @fixme when tsys is channel based
+    for (uInt i=0; i<nchan; ++i) {
+      Vector<Float> specs = specCol.getColumn(Slicer(Slice(i)));
+      MaskedArray<Float> ma = maskedArray(specs,flags);
+      outspec[i] = median(ma);
+      if ( allEQ(ma.getMask(), False) )
+        outflag[i] = userflag;// flag data
+    }
+    outtsys[0] = median(tsysCol.getColumn());
+    specColOut.put(outrowCount, outspec);
+    flagColOut.put(outrowCount, outflag);
+    tsysColOut.put(outrowCount, outtsys);
+
+    ++outrowCount;
+    ++iter;
+  }
+  return out;
+}
+
+CountedPtr< Scantable > STMath::getScantable(const CountedPtr< Scantable >& in,
+                                             bool droprows)
+{
+  if (insitu_) return in;
+  else {
+    // clone
+    Scantable* tabp = new Scantable(*in, Bool(droprows));
+    return CountedPtr<Scantable>(tabp);
+  }
+}
+
+CountedPtr< Scantable > STMath::unaryOperate( const CountedPtr< Scantable >& in,
+                                              float val,
+                                              const std::string& mode,
+                                              bool tsys )
+{
+  // modes are "ADD" and "MUL"
+  CountedPtr< Scantable > out = getScantable(in, false);
+  Table& tab = out->table();
+  ArrayColumn<Float> specCol(tab,"SPECTRA");
+  ArrayColumn<Float> tsysCol(tab,"TSYS");
+  for (uInt i=0; i<tab.nrow(); ++i) {
+    Vector<Float> spec;
+    Vector<Float> ts;
+    specCol.get(i, spec);
+    tsysCol.get(i, ts);
+    if (mode == "MUL") {
+      spec *= val;
+      specCol.put(i, spec);
+      if ( tsys ) {
+        ts *= val;
+        tsysCol.put(i, ts);
+      }
+    } else if ( mode == "ADD" ) {
+      spec += val;
+      specCol.put(i, spec);
+      if ( tsys ) {
+        ts += val;
+        tsysCol.put(i, ts);
+      }
+    }
+  }
+  return out;
+}
+
+MaskedArray<Float> STMath::maskedArray( const Vector<Float>& s,
+                                        const Vector<uChar>& f)
+{
+  Vector<Bool> mask;
+  mask.resize(f.shape());
+  convertArray(mask, f);
+  return MaskedArray<Float>(s,!mask);
+}
+
+Vector<uChar> STMath::flagsFromMA(const MaskedArray<Float>& ma)
+{
+  const Vector<Bool>& m = ma.getMask();
+  Vector<uChar> flags(m.shape());
+  convertArray(flags, !m);
+  return flags;
+}
+
+CountedPtr< Scantable > STMath::autoQuotient( const CountedPtr< Scantable >& in,
+                                              const std::string & mode,
+                                              bool preserve )
+{
+  /// @todo make other modes available
+  /// modes should be "nearest", "pair"
+  // make this operation non insitu
+  const Table& tin = in->table();
+  Table ons = tin(tin.col("SRCTYPE") == Int(0));
+  Table offs = tin(tin.col("SRCTYPE") == Int(1));
+  if ( offs.nrow() == 0 )
+    throw(AipsError("No 'off' scans present."));
+  // put all "on" scans into output table
+
+  bool insitu = insitu_;
+  setInsitu(false);
+  CountedPtr< Scantable > out = getScantable(in, true);
+  setInsitu(insitu);
+  Table& tout = out->table();
+
+  TableCopy::copyRows(tout, ons);
+  TableRow row(tout);
+  ROScalarColumn<Double> offtimeCol(offs, "TIME");
+
+  ArrayColumn<Float> outspecCol(tout, "SPECTRA");
+  ROArrayColumn<Float> outtsysCol(tout, "TSYS");
+  ArrayColumn<uChar> outflagCol(tout, "FLAGTRA");
+  for (uInt i=0; i < tout.nrow(); ++i) {
+    const TableRecord& rec = row.get(i);
+    Double ontime = rec.asDouble("TIME");
+    ROScalarColumn<Double> offtimeCol(offs, "TIME");
+    Double mindeltat = min(abs(offtimeCol.getColumn() - ontime));
+    Table sel = offs( abs(offs.col("TIME")-ontime) <= mindeltat
+                       && offs.col("BEAMNO") == Int(rec.asuInt("BEAMNO"))
+                       && offs.col("IFNO") == Int(rec.asuInt("IFNO"))
+                       && offs.col("POLNO") == Int(rec.asuInt("POLNO")) );
+
+    TableRow offrow(sel);
+    const TableRecord& offrec = offrow.get(0);//should only be one row
+    RORecordFieldPtr< Array<Float> > specoff(offrec, "SPECTRA");
+    RORecordFieldPtr< Array<Float> > tsysoff(offrec, "TSYS");
+    RORecordFieldPtr< Array<uChar> > flagoff(offrec, "FLAGTRA");
+    /// @fixme this assumes tsys is a scalar not vector
+    Float tsysoffscalar = (*tsysoff)(IPosition(1,0));
+    Vector<Float> specon, tsyson;
+    outtsysCol.get(i, tsyson);
+    outspecCol.get(i, specon);
+    Vector<uChar> flagon;
+    outflagCol.get(i, flagon);
+    MaskedArray<Float> mon = maskedArray(specon, flagon);
+    MaskedArray<Float> moff = maskedArray(*specoff, *flagoff);
+    MaskedArray<Float> quot = (tsysoffscalar * mon / moff);
+    if (preserve) {
+      quot -= tsysoffscalar;
+    } else {
+      quot -= tsyson[0];
+    }
+    outspecCol.put(i, quot.getArray());
+    outflagCol.put(i, flagsFromMA(quot));
+  }
+  // renumber scanno
+  TableIterator it(tout, "SCANNO");
+  uInt i = 0;
+  while ( !it.pastEnd() ) {
+    Table t = it.table();
+    TableVector<uInt> vec(t, "SCANNO");
+    vec = i;
+    ++i;
+    ++it;
+  }
+  return out;
+}
+
+
+CountedPtr< Scantable > STMath::quotient( const CountedPtr< Scantable > & on,
+                                          const CountedPtr< Scantable > & off,
+                                          bool preserve )
+{
+  bool insitu = insitu_;
+  if ( ! on->conformant(*off) ) {
+    throw(AipsError("'on' and 'off' scantables are not conformant."));
+  }
+  setInsitu(false);
+  CountedPtr< Scantable > out = getScantable(on, false);
+  setInsitu(insitu);
+  Table& tout = out->table();
+  const Table& toff = off->table();
+  TableIterator sit(tout, "SCANNO");
+  TableIterator s2it(toff, "SCANNO");
+  while ( !sit.pastEnd() ) {
+    Table ton = sit.table();
+    TableRow row(ton);
+    Table t = s2it.table();
+    ArrayColumn<Float> outspecCol(ton, "SPECTRA");
+    ROArrayColumn<Float> outtsysCol(ton, "TSYS");
+    ArrayColumn<uChar> outflagCol(ton, "FLAGTRA");
+    for (uInt i=0; i < ton.nrow(); ++i) {
+      const TableRecord& rec = row.get(i);
+      Table offsel = t( t.col("BEAMNO") == Int(rec.asuInt("BEAMNO"))
+                          && t.col("IFNO") == Int(rec.asuInt("IFNO"))
+                          && t.col("POLNO") == Int(rec.asuInt("POLNO")) );
+      if ( offsel.nrow() == 0 )
+        throw AipsError("STMath::quotient: no matching off");
+      TableRow offrow(offsel);
+      const TableRecord& offrec = offrow.get(0);//should be ncycles - take first
+      RORecordFieldPtr< Array<Float> > specoff(offrec, "SPECTRA");
+      RORecordFieldPtr< Array<Float> > tsysoff(offrec, "TSYS");
+      RORecordFieldPtr< Array<uChar> > flagoff(offrec, "FLAGTRA");
+      Float tsysoffscalar = (*tsysoff)(IPosition(1,0));
+      Vector<Float> specon, tsyson;
+      outtsysCol.get(i, tsyson);
+      outspecCol.get(i, specon);
+      Vector<uChar> flagon;
+      outflagCol.get(i, flagon);
+      MaskedArray<Float> mon = maskedArray(specon, flagon);
+      MaskedArray<Float> moff = maskedArray(*specoff, *flagoff);
+      MaskedArray<Float> quot = (tsysoffscalar * mon / moff);
+      if (preserve) {
+        quot -= tsysoffscalar;
+      } else {
+        quot -= tsyson[0];
+      }
+      outspecCol.put(i, quot.getArray());
+      outflagCol.put(i, flagsFromMA(quot));
+    }
+    ++sit;
+    ++s2it;
+    // take the first off for each on scan which doesn't have a
+    // matching off scan
+    // non <= noff:  matching pairs, non > noff matching pairs then first off
+    if ( s2it.pastEnd() ) s2it.reset();
+  }
+  return out;
+}
+
+
+CountedPtr< Scantable > STMath::freqSwitch( const CountedPtr< Scantable >& in )
+{
+  // make copy or reference
+  CountedPtr< Scantable > out = getScantable(in, false);
+  Table& tout = out->table();
+  Block<String> cols(4);
+  cols[0] = String("SCANNO");
+  cols[1] = String("CYCLENO");
+  cols[2] = String("BEAMNO");
+  cols[3] = String("POLNO");
+  TableIterator iter(tout, cols);
+  while (!iter.pastEnd()) {
+    Table subt = iter.table();
+    // this should leave us with two rows for the two IFs....if not ignore
+    if (subt.nrow() != 2 ) {
+      continue;
+    }
+    ArrayColumn<Float> specCol(subt, "SPECTRA");
+    ArrayColumn<Float> tsysCol(subt, "TSYS");
+    ArrayColumn<uChar> flagCol(subt, "FLAGTRA");
+    Vector<Float> onspec,offspec, ontsys, offtsys;
+    Vector<uChar> onflag, offflag;
+    tsysCol.get(0, ontsys);   tsysCol.get(1, offtsys);
+    specCol.get(0, onspec);   specCol.get(1, offspec);
+    flagCol.get(0, onflag);   flagCol.get(1, offflag);
+    MaskedArray<Float> on  = maskedArray(onspec, onflag);
+    MaskedArray<Float> off = maskedArray(offspec, offflag);
+    MaskedArray<Float> oncopy = on.copy();
+
+    on /= off; on -= 1.0f;
+    on *= ontsys[0];
+    off /= oncopy; off -= 1.0f;
+    off *= offtsys[0];
+    specCol.put(0, on.getArray());
+    const Vector<Bool>& m0 = on.getMask();
+    Vector<uChar> flags0(m0.shape());
+    convertArray(flags0, !m0);
+    flagCol.put(0, flags0);
+
+    specCol.put(1, off.getArray());
+    const Vector<Bool>& m1 = off.getMask();
+    Vector<uChar> flags1(m1.shape());
+    convertArray(flags1, !m1);
+    flagCol.put(1, flags1);
+    ++iter;
+  }
+
+  return out;
+}
+
+std::vector< float > STMath::statistic( const CountedPtr< Scantable > & in,
+                                        const std::vector< bool > & mask,
+                                        const std::string& which )
+{
+
+  Vector<Bool> m(mask);
+  const Table& tab = in->table();
+  ROArrayColumn<Float> specCol(tab, "SPECTRA");
+  ROArrayColumn<uChar> flagCol(tab, "FLAGTRA");
+  std::vector<float> out;
+  for (uInt i=0; i < tab.nrow(); ++i ) {
+    Vector<Float> spec; specCol.get(i, spec);
+    Vector<uChar> flag; flagCol.get(i, flag);
+    MaskedArray<Float> ma  = maskedArray(spec, flag);
+    float outstat = 0.0;
+    if ( spec.nelements() == m.nelements() ) {
+      outstat = mathutil::statistics(which, ma(m));
+    } else {
+      outstat = mathutil::statistics(which, ma);
+    }
+    out.push_back(outstat);
+  }
+  return out;
+}
+
+CountedPtr< Scantable > STMath::bin( const CountedPtr< Scantable > & in,
+                                     int width )
+{
+  if ( !in->getSelection().empty() ) throw(AipsError("Can't bin subset of the data."));
+  CountedPtr< Scantable > out = getScantable(in, false);
+  Table& tout = out->table();
+  out->frequencies().rescale(width, "BIN");
+  ArrayColumn<Float> specCol(tout, "SPECTRA");
+  ArrayColumn<uChar> flagCol(tout, "FLAGTRA");
+  for (uInt i=0; i < tout.nrow(); ++i ) {
+    MaskedArray<Float> main  = maskedArray(specCol(i), flagCol(i));
+    MaskedArray<Float> maout;
+    LatticeUtilities::bin(maout, main, 0, Int(width));
+    /// @todo implement channel based tsys binning
+    specCol.put(i, maout.getArray());
+    flagCol.put(i, flagsFromMA(maout));
+    // take only the first binned spectrum's length for the deprecated
+    // global header item nChan
+    if (i==0) tout.rwKeywordSet().define(String("nChan"),
+                                       Int(maout.getArray().nelements()));
+  }
+  return out;
+}
+
+CountedPtr< Scantable > STMath::resample( const CountedPtr< Scantable >& in,
+                                          const std::string& method,
+                                          float width )
+//
+// Should add the possibility of width being specified in km/s. This means
+// that for each freqID (SpectralCoordinate) we will need to convert to an
+// average channel width (say at the reference pixel).  Then we would need
+// to be careful to make sure each spectrum (of different freqID)
+// is the same length.
+//
+{
+  //InterpolateArray1D<Double,Float>::InterpolationMethod interp;
+  Int interpMethod(stringToIMethod(method));
+
+  CountedPtr< Scantable > out = getScantable(in, false);
+  Table& tout = out->table();
+
+// Resample SpectralCoordinates (one per freqID)
+  out->frequencies().rescale(width, "RESAMPLE");
+  TableIterator iter(tout, "IFNO");
+  TableRow row(tout);
+  while ( !iter.pastEnd() ) {
+    Table tab = iter.table();
+    ArrayColumn<Float> specCol(tab, "SPECTRA");
+    //ArrayColumn<Float> tsysCol(tout, "TSYS");
+    ArrayColumn<uChar> flagCol(tab, "FLAGTRA");
+    Vector<Float> spec;
+    Vector<uChar> flag;
+    specCol.get(0,spec); // the number of channels should be constant per IF
+    uInt nChanIn = spec.nelements();
+    Vector<Float> xIn(nChanIn); indgen(xIn);
+    Int fac =  Int(nChanIn/width);
+    Vector<Float> xOut(fac+10); // 10 to be safe - resize later
+    uInt k = 0;
+    Float x = 0.0;
+    while (x < Float(nChanIn) ) {
+      xOut(k) = x;
+      k++;
+      x += width;
+    }
+    uInt nChanOut = k;
+    xOut.resize(nChanOut, True);
+    // process all rows for this IFNO
+    Vector<Float> specOut;
+    Vector<Bool> maskOut;
+    Vector<uChar> flagOut;
+    for (uInt i=0; i < tab.nrow(); ++i) {
+      specCol.get(i, spec);
+      flagCol.get(i, flag);
+      Vector<Bool> mask(flag.nelements());
+      convertArray(mask, flag);
+
+      IPosition shapeIn(spec.shape());
+      //sh.nchan = nChanOut;
+      InterpolateArray1D<Float,Float>::interpolate(specOut, maskOut, xOut,
+                                                   xIn, spec, mask,
+                                                   interpMethod, True, True);
+      /// @todo do the same for channel based Tsys
+      flagOut.resize(maskOut.nelements());
+      convertArray(flagOut, maskOut);
+      specCol.put(i, specOut);
+      flagCol.put(i, flagOut);
+    }
+    ++iter;
+  }
+
+  return out;
+}
+
+STMath::imethod STMath::stringToIMethod(const std::string& in)
+{
+  static STMath::imap lookup;
+
+  // initialize the lookup table if necessary
+  if ( lookup.empty() ) {
+    lookup["nearest"]   = InterpolateArray1D<Double,Float>::nearestNeighbour;
+    lookup["linear"] = InterpolateArray1D<Double,Float>::linear;
+    lookup["cubic"]  = InterpolateArray1D<Double,Float>::cubic;
+    lookup["spline"]  = InterpolateArray1D<Double,Float>::spline;
+  }
+
+  STMath::imap::const_iterator iter = lookup.find(in);
+
+  if ( lookup.end() == iter ) {
+    std::string message = in;
+    message += " is not a valid interpolation mode";
+    throw(AipsError(message));
+  }
+  return iter->second;
+}
+
+WeightType STMath::stringToWeight(const std::string& in)
+{
+  static std::map<std::string, WeightType> lookup;
+
+  // initialize the lookup table if necessary
+  if ( lookup.empty() ) {
+    lookup["NONE"]   = asap::NONE;
+    lookup["TINT"] = asap::TINT;
+    lookup["TINTSYS"]  = asap::TINTSYS;
+    lookup["TSYS"]  = asap::TSYS;
+    lookup["VAR"]  = asap::VAR;
+  }
+
+  std::map<std::string, WeightType>::const_iterator iter = lookup.find(in);
+
+  if ( lookup.end() == iter ) {
+    std::string message = in;
+    message += " is not a valid weighting mode";
+    throw(AipsError(message));
+  }
+  return iter->second;
+}
+
+CountedPtr< Scantable > STMath::gainElevation( const CountedPtr< Scantable >& in,
+                                               const vector< float > & coeff,
+                                               const std::string & filename,
+                                               const std::string& method)
+{
+  // Get elevation data from Scantable and convert to degrees
+  CountedPtr< Scantable > out = getScantable(in, false);
+  Table& tab = out->table();
+  ROScalarColumn<Float> elev(tab, "ELEVATION");
+  Vector<Float> x = elev.getColumn();
+  x *= Float(180 / C::pi);                        // Degrees
+
+  Vector<Float> coeffs(coeff);
+  const uInt nc = coeffs.nelements();
+  if ( filename.length() > 0 && nc > 0 ) {
+    throw(AipsError("You must choose either polynomial coefficients or an ascii file, not both"));
+  }
+
+  // Correct
+  if ( nc > 0 || filename.length() == 0 ) {
+    // Find instrument
+    Bool throwit = True;
+    Instrument inst =
+      STAttr::convertInstrument(tab.keywordSet().asString("AntennaName"),
+                                throwit);
+
+    // Set polynomial
+    Polynomial<Float>* ppoly = 0;
+    Vector<Float> coeff;
+    String msg;
+    if ( nc > 0 ) {
+      ppoly = new Polynomial<Float>(nc);
+      coeff = coeffs;
+      msg = String("user");
+    } else {
+      STAttr sdAttr;
+      coeff = sdAttr.gainElevationPoly(inst);
+      ppoly = new Polynomial<Float>(3);
+      msg = String("built in");
+    }
+
+    if ( coeff.nelements() > 0 ) {
+      ppoly->setCoefficients(coeff);
+    } else {
+      delete ppoly;
+      throw(AipsError("There is no known gain-elevation polynomial known for this instrument"));
+    }
+    ostringstream oss;
+    oss << "Making polynomial correction with " << msg << " coefficients:" << endl;
+    oss << "   " <<  coeff;
+    pushLog(String(oss));
+    const uInt nrow = tab.nrow();
+    Vector<Float> factor(nrow);
+    for ( uInt i=0; i < nrow; ++i ) {
+      factor[i] = 1.0 / (*ppoly)(x[i]);
+    }
+    delete ppoly;
+    scaleByVector(tab, factor, true);
+
+  } else {
+    // Read and correct
+    pushLog("Making correction from ascii Table");
+    scaleFromAsciiTable(tab, filename, method, x, true);
+  }
+  return out;
+}
+
+void STMath::scaleFromAsciiTable(Table& in, const std::string& filename,
+                                 const std::string& method,
+                                 const Vector<Float>& xout, bool dotsys)
+{
+
+// Read gain-elevation ascii file data into a Table.
+
+  String formatString;
+  Table tbl = readAsciiTable(formatString, Table::Memory, filename, "", "", False);
+  scaleFromTable(in, tbl, method, xout, dotsys);
+}
+
+void STMath::scaleFromTable(Table& in,
+                            const Table& table,
+                            const std::string& method,
+                            const Vector<Float>& xout, bool dotsys)
+{
+
+  ROScalarColumn<Float> geElCol(table, "ELEVATION");
+  ROScalarColumn<Float> geFacCol(table, "FACTOR");
+  Vector<Float> xin = geElCol.getColumn();
+  Vector<Float> yin = geFacCol.getColumn();
+  Vector<Bool> maskin(xin.nelements(),True);
+
+  // Interpolate (and extrapolate) with desired method
+
+  InterpolateArray1D<Double,Float>::InterpolationMethod interp = stringToIMethod(method);
+
+   Vector<Float> yout;
+   Vector<Bool> maskout;
+   InterpolateArray1D<Float,Float>::interpolate(yout, maskout, xout,
+                                                xin, yin, maskin, interp,
+                                                True, True);
+
+   scaleByVector(in, Float(1.0)/yout, dotsys);
+}
+
+void STMath::scaleByVector( Table& in,
+                            const Vector< Float >& factor,
+                            bool dotsys )
+{
+  uInt nrow = in.nrow();
+  if ( factor.nelements() != nrow ) {
+    throw(AipsError("factors.nelements() != table.nelements()"));
+  }
+  ArrayColumn<Float> specCol(in, "SPECTRA");
+  ArrayColumn<uChar> flagCol(in, "FLAGTRA");
+  ArrayColumn<Float> tsysCol(in, "TSYS");
+  for (uInt i=0; i < nrow; ++i) {
+    MaskedArray<Float> ma  = maskedArray(specCol(i), flagCol(i));
+    ma *= factor[i];
+    specCol.put(i, ma.getArray());
+    flagCol.put(i, flagsFromMA(ma));
+    if ( dotsys ) {
+      Vector<Float> tsys = tsysCol(i);
+      tsys *= factor[i];
+      tsysCol.put(i,tsys);
+    }
+  }
+}
+
+CountedPtr< Scantable > STMath::convertFlux( const CountedPtr< Scantable >& in,
+                                             float d, float etaap,
+                                             float jyperk )
+{
+  CountedPtr< Scantable > out = getScantable(in, false);
+  Table& tab = in->table();
+  Unit fluxUnit(tab.keywordSet().asString("FluxUnit"));
+  Unit K(String("K"));
+  Unit JY(String("Jy"));
+
+  bool tokelvin = true;
+  Double cfac = 1.0;
+
+  if ( fluxUnit == JY ) {
+    pushLog("Converting to K");
+    Quantum<Double> t(1.0,fluxUnit);
+    Quantum<Double> t2 = t.get(JY);
+    cfac = (t2 / t).getValue();               // value to Jy
+
+    tokelvin = true;
+    out->setFluxUnit("K");
+  } else if ( fluxUnit == K ) {
+    pushLog("Converting to Jy");
+    Quantum<Double> t(1.0,fluxUnit);
+    Quantum<Double> t2 = t.get(K);
+    cfac = (t2 / t).getValue();              // value to K
+
+    tokelvin = false;
+    out->setFluxUnit("Jy");
+  } else {
+    throw(AipsError("Unrecognized brightness units in Table - must be consistent with Jy or K"));
+  }
+  // Make sure input values are converted to either Jy or K first...
+  Float factor = cfac;
+
+  // Select method
+  if (jyperk > 0.0) {
+    factor *= jyperk;
+    if ( tokelvin ) factor = 1.0 / jyperk;
+    ostringstream oss;
+    oss << "Jy/K = " << jyperk;
+    pushLog(String(oss));
+    Vector<Float> factors(tab.nrow(), factor);
+    scaleByVector(tab,factors, false);
+  } else if ( etaap > 0.0) {
+    Instrument inst =
+      STAttr::convertInstrument(tab.keywordSet().asString("AntennaName"), True);
+    STAttr sda;
+    if (d < 0) d = sda.diameter(inst);
+    jyperk = STAttr::findJyPerK(etaap, d);
+    ostringstream oss;
+    oss << "Jy/K = " << jyperk;
+    pushLog(String(oss));
+    factor *= jyperk;
+    if ( tokelvin ) {
+      factor = 1.0 / factor;
+    }
+    Vector<Float> factors(tab.nrow(), factor);
+    scaleByVector(tab, factors, False);
+  } else {
+
+    // OK now we must deal with automatic look up of values.
+    // We must also deal with the fact that the factors need
+    // to be computed per IF and may be different and may
+    // change per integration.
+
+    pushLog("Looking up conversion factors");
+    convertBrightnessUnits(out, tokelvin, cfac);
+  }
+
+  return out;
+}
+
+void STMath::convertBrightnessUnits( CountedPtr<Scantable>& in,
+                                     bool tokelvin, float cfac )
+{
+  Table& table = in->table();
+  Instrument inst =
+    STAttr::convertInstrument(table.keywordSet().asString("AntennaName"), True);
+  TableIterator iter(table, "FREQ_ID");
+  STFrequencies stfreqs = in->frequencies();
+  STAttr sdAtt;
+  while (!iter.pastEnd()) {
+    Table tab = iter.table();
+    ArrayColumn<Float> specCol(tab, "SPECTRA");
+    ArrayColumn<uChar> flagCol(tab, "FLAGTRA");
+    ROScalarColumn<uInt> freqidCol(tab, "FREQ_ID");
+    MEpoch::ROScalarColumn timeCol(tab, "TIME");
+
+    uInt freqid; freqidCol.get(0, freqid);
+    Vector<Float> tmpspec; specCol.get(0, tmpspec);
+    // STAttr.JyPerK has a Vector interface... change sometime.
+    Vector<Float> freqs(1,stfreqs.getRefFreq(freqid, tmpspec.nelements()));
+    for ( uInt i=0; i<tab.nrow(); ++i) {
+      Float jyperk = (sdAtt.JyPerK(inst, timeCol(i), freqs))[0];
+      Float factor = cfac * jyperk;
+      if ( tokelvin ) factor = Float(1.0) / factor;
+      MaskedArray<Float> ma  = maskedArray(specCol(i), flagCol(i));
+      ma *= factor;
+      specCol.put(i, ma.getArray());
+      flagCol.put(i, flagsFromMA(ma));
+    }
+  ++iter;
+  }
+}
+
+CountedPtr< Scantable > STMath::opacity( const CountedPtr< Scantable > & in,
+                                         float tau )
+{
+  CountedPtr< Scantable > out = getScantable(in, false);
+
+  Table tab = out->table();
+  ROScalarColumn<Float> elev(tab, "ELEVATION");
+  ArrayColumn<Float> specCol(tab, "SPECTRA");
+  ArrayColumn<uChar> flagCol(tab, "FLAGTRA");
+  for ( uInt i=0; i<tab.nrow(); ++i) {
+    Float zdist = Float(C::pi_2) - elev(i);
+    Float factor = exp(tau)/cos(zdist);
+    MaskedArray<Float> ma = maskedArray(specCol(i), flagCol(i));
+    ma *= factor;
+    specCol.put(i, ma.getArray());
+    flagCol.put(i, flagsFromMA(ma));
+  }
+  return out;
+}
+
+CountedPtr< Scantable > STMath::smooth( const CountedPtr< Scantable >& in,
+                                        const std::string& kernel, float width )
+{
+  CountedPtr< Scantable > out = getScantable(in, false);
+  Table& table = out->table();
+  VectorKernel::KernelTypes type = VectorKernel::toKernelType(kernel);
+  // same IFNO should have same no of channels
+  // this saves overhead
+  TableIterator iter(table, "IFNO");
+  while (!iter.pastEnd()) {
+    Table tab = iter.table();
+    ArrayColumn<Float> specCol(tab, "SPECTRA");
+    ArrayColumn<uChar> flagCol(tab, "FLAGTRA");
+    Vector<Float> tmpspec; specCol.get(0, tmpspec);
+    uInt nchan = tmpspec.nelements();
+    Vector<Float> kvec = VectorKernel::make(type, width, nchan, True, False);
+    Convolver<Float> conv(kvec, IPosition(1,nchan));
+    Vector<Float> spec;
+    Vector<uChar> flag;
+    for ( uInt i=0; i<tab.nrow(); ++i) {
+      specCol.get(i, spec);
+      flagCol.get(i, flag);
+      Vector<Bool> mask(flag.nelements());
+      convertArray(mask, flag);
+      Vector<Float> specout;
+      if ( type == VectorKernel::HANNING ) {
+        Vector<Bool> maskout;
+        mathutil::hanning(specout, maskout, spec , !mask);
+        convertArray(flag, !maskout);
+        flagCol.put(i, flag);
+        specCol.put(i, specout);
+     } else {
+        mathutil::replaceMaskByZero(specout, mask);
+        conv.linearConv(specout, spec);
+        specCol.put(i, specout);
+      }
+    }
+    ++iter;
+  }
+  return out;
+}
+
+CountedPtr< Scantable >
+  STMath::merge( const std::vector< CountedPtr < Scantable > >& in )
+{
+  if ( in.size() < 2 ) {
+    throw(AipsError("Need at least two scantables to perform a merge."));
+  }
+  std::vector<CountedPtr < Scantable > >::const_iterator it = in.begin();
+  bool insitu = insitu_;
+  setInsitu(false);
+  CountedPtr< Scantable > out = getScantable(*it, false);
+  setInsitu(insitu);
+  Table& tout = out->table();
+  ScalarColumn<uInt> freqidcol(tout,"FREQ_ID"), molidcol(tout, "MOLECULE_ID");
+  ScalarColumn<uInt> scannocol(tout,"SCANNO"), focusidcol(tout,"FOCUS_ID");
+  // Renumber SCANNO to be 0-based
+  Vector<uInt> scannos = scannocol.getColumn();
+  uInt offset = min(scannos);
+  scannos -= offset;
+  scannocol.putColumn(scannos);
+  uInt newscanno = max(scannos)+1;
+  ++it;
+  while ( it != in.end() ){
+    if ( ! (*it)->conformant(*out) ) {
+      // log message: "ignoring scantable i, as it isn't
+      // conformant with the other(s)"
+      cerr << "oh oh" << endl;
+      ++it;
+      continue;
+    }
+    out->appendToHistoryTable((*it)->history());
+    const Table& tab = (*it)->table();
+    TableIterator scanit(tab, "SCANNO");
+    while (!scanit.pastEnd()) {
+      TableIterator freqit(scanit.table(), "FREQ_ID");
+      while ( !freqit.pastEnd() ) {
+        Table thetab = freqit.table();
+        uInt nrow = tout.nrow();
+        //tout.addRow(thetab.nrow());
+        TableCopy::copyRows(tout, thetab, nrow, 0, thetab.nrow());
+        ROTableRow row(thetab);
+        for ( uInt i=0; i<thetab.nrow(); ++i) {
+          uInt k = nrow+i;
+          scannocol.put(k, newscanno);
+          const TableRecord& rec = row.get(i);
+          Double rv,rp,inc;
+          (*it)->frequencies().getEntry(rp, rv, inc, rec.asuInt("FREQ_ID"));
+          uInt id;
+          id = out->frequencies().addEntry(rp, rv, inc);
+          freqidcol.put(k,id);
+          String name,fname;Double rf;
+          (*it)->molecules().getEntry(rf, name, fname, rec.asuInt("MOLECULE_ID"));
+          id = out->molecules().addEntry(rf, name, fname);
+          molidcol.put(k, id);
+          Float frot,fax,ftan,fhand,fmount,fuser, fxy, fxyp;
+          (*it)->focus().getEntry(fax, ftan, frot, fhand,
+                                  fmount,fuser, fxy, fxyp,
+                                  rec.asuInt("FOCUS_ID"));
+          id = out->focus().addEntry(fax, ftan, frot, fhand,
+                                     fmount,fuser, fxy, fxyp);
+          focusidcol.put(k, id);
+        }
+        ++freqit;
+      }
+      ++newscanno;
+      ++scanit;
+    }
+    ++it;
+  }
+  return out;
+}
+
+CountedPtr< Scantable >
+  STMath::invertPhase( const CountedPtr < Scantable >& in )
+{
+  return applyToPol(in, &STPol::invertPhase, Float(0.0));
+}
+
+CountedPtr< Scantable >
+  STMath::rotateXYPhase( const CountedPtr < Scantable >& in, float phase )
+{
+   return applyToPol(in, &STPol::rotatePhase, Float(phase));
+}
+
+CountedPtr< Scantable >
+  STMath::rotateLinPolPhase( const CountedPtr < Scantable >& in, float phase )
+{
+  return applyToPol(in, &STPol::rotateLinPolPhase, Float(phase));
+}
+
+CountedPtr< Scantable > STMath::applyToPol( const CountedPtr<Scantable>& in,
+                                             STPol::polOperation fptr,
+                                             Float phase )
+{
+  CountedPtr< Scantable > out = getScantable(in, false);
+  Table& tout = out->table();
+  Block<String> cols(4);
+  cols[0] = String("SCANNO");
+  cols[1] = String("BEAMNO");
+  cols[2] = String("IFNO");
+  cols[3] = String("CYCLENO");
+  TableIterator iter(tout, cols);
+  STPol* stpol = STPol::getPolClass(out->factories_, out->getPolType() );
+  while (!iter.pastEnd()) {
+    Table t = iter.table();
+    ArrayColumn<Float> speccol(t, "SPECTRA");
+    ScalarColumn<uInt> focidcol(t, "FOCUS_ID");
+    ScalarColumn<Float> parancol(t, "PARANGLE");
+    Matrix<Float> pols = speccol.getColumn();
+    try {
+      stpol->setSpectra(pols);
+      Float fang,fhand,parang;
+      fang = in->focusTable_.getTotalFeedAngle(focidcol(0));
+      fhand = in->focusTable_.getFeedHand(focidcol(0));
+      parang = parancol(0);
+      /// @todo re-enable this
+      // disable total feed angle to support paralactifying Caswell style
+      stpol->setPhaseCorrections(parang, -parang, fhand);
+      (stpol->*fptr)(phase);
+      speccol.putColumn(stpol->getSpectra());
+      Matrix<Float> tmp = stpol->getSpectra();
+    } catch (AipsError& e) {
+      delete stpol;stpol=0;
+      throw(e);
+    }
+    ++iter;
+  }
+  delete stpol;stpol=0;
+  return out;
+}
+
+CountedPtr< Scantable >
+  STMath::swapPolarisations( const CountedPtr< Scantable > & in )
+{
+  CountedPtr< Scantable > out = getScantable(in, false);
+  Table& tout = out->table();
+  Table t0 = tout(tout.col("POLNO") == 0);
+  Table t1 = tout(tout.col("POLNO") == 1);
+  if ( t0.nrow() != t1.nrow() )
+    throw(AipsError("Inconsistent number of polarisations"));
+  ArrayColumn<Float> speccol0(t0, "SPECTRA");
+  ArrayColumn<uChar> flagcol0(t0, "FLAGTRA");
+  ArrayColumn<Float> speccol1(t1, "SPECTRA");
+  ArrayColumn<uChar> flagcol1(t1, "FLAGTRA");
+  Matrix<Float> s0 = speccol0.getColumn();
+  Matrix<uChar> f0 = flagcol0.getColumn();
+  speccol0.putColumn(speccol1.getColumn());
+  flagcol0.putColumn(flagcol1.getColumn());
+  speccol1.putColumn(s0);
+  flagcol1.putColumn(f0);
+  return out;
+}
+
+CountedPtr< Scantable >
+  STMath::averagePolarisations( const CountedPtr< Scantable > & in,
+                                const std::vector<bool>& mask,
+                                const std::string& weight )
+{
+  if (in->getPolType() != "linear"  || in->npol() != 2 )
+    throw(AipsError("averagePolarisations can only be applied to two linear polarisations."));
+  bool insitu = insitu_;
+  setInsitu(false);
+  CountedPtr< Scantable > pols = getScantable(in, false);
+  setInsitu(insitu);
+  Table& tout = pols->table();
+  // give all rows the same POLNO
+  TableVector<uInt> vec(tout, "POLNO");
+  vec = 0;
+  pols->table_.rwKeywordSet().define("nPol", Int(1));
+  std::vector<CountedPtr<Scantable> > vpols;
+  vpols.push_back(pols);
+  CountedPtr< Scantable > out = average(vpols, mask, weight, "NONE");
+  return out;
+}
+
+CountedPtr< Scantable >
+  STMath::averageBeams( const CountedPtr< Scantable > & in,
+                        const std::vector<bool>& mask,
+                        const std::string& weight )
+{
+  bool insitu = insitu_;
+  setInsitu(false);
+  CountedPtr< Scantable > beams = getScantable(in, false);
+  setInsitu(insitu);
+  Table& tout = beams->table();
+  // give all rows the same BEAMNO
+  TableVector<uInt> vec(tout, "BEAMNO");
+  vec = 0;
+  beams->table_.rwKeywordSet().define("nBeam", Int(1));
+  std::vector<CountedPtr<Scantable> > vbeams;
+  vbeams.push_back(beams);
+  CountedPtr< Scantable > out = average(vbeams, mask, weight, "NONE");
+  return out;
+}
+
+
+CountedPtr< Scantable >
+  asap::STMath::frequencyAlign( const CountedPtr< Scantable > & in,
+                                const std::string & refTime,
+                                const std::string & method)
+{
+  // clone as this is not working insitu
+  bool insitu = insitu_;
+  setInsitu(false);
+  CountedPtr< Scantable > out = getScantable(in, false);
+  setInsitu(insitu);
+  Table& tout = out->table();
+  // Get reference Epoch to time of first row or given String
+  Unit DAY(String("d"));
+  MEpoch::Ref epochRef(in->getTimeReference());
+  MEpoch refEpoch;
+  if (refTime.length()>0) {
+    Quantum<Double> qt;
+    if (MVTime::read(qt,refTime)) {
+      MVEpoch mv(qt);
+      refEpoch = MEpoch(mv, epochRef);
+   } else {
+      throw(AipsError("Invalid format for Epoch string"));
+   }
+  } else {
+    refEpoch = in->timeCol_(0);
+  }
+  MPosition refPos = in->getAntennaPosition();
+
+  InterpolateArray1D<Double,Float>::InterpolationMethod interp = stringToIMethod(method);
+  // test if user frame is different to base frame
+  if ( in->frequencies().getFrameString(true)
+       == in->frequencies().getFrameString(false) ) {
+    throw(AipsError("Can't convert as no output frame has been set"
+                    " (use set_freqframe) or it is aligned already."));
+  }
+  MFrequency::Types system = in->frequencies().getFrame();
+  MVTime mvt(refEpoch.getValue());
+  String epochout = mvt.string(MVTime::YMD) + String(" (") + refEpoch.getRefString() + String(")");
+  ostringstream oss;
+  oss << "Aligned at reference Epoch " << epochout
+      << " in frame " << MFrequency::showType(system);
+  pushLog(String(oss));
+  // set up the iterator
+  Block<String> cols(4);
+  // select by constant direction
+  cols[0] = String("SRCNAME");
+  cols[1] = String("BEAMNO");
+  // select by IF ( no of channels varies over this )
+  cols[2] = String("IFNO");
+  // select by restfrequency
+  cols[3] = String("MOLECULE_ID");
+  TableIterator iter(tout, cols);
+  while ( !iter.pastEnd() ) {
+    Table t = iter.table();
+    MDirection::ROScalarColumn dirCol(t, "DIRECTION");
+    TableIterator fiter(t, "FREQ_ID");
+    // determine nchan from the first row. This should work as
+    // we are iterating over BEAMNO and IFNO    // we should have constant direction
+
+    ROArrayColumn<Float> sCol(t, "SPECTRA");
+    MDirection direction = dirCol(0);
+    uInt nchan = sCol(0).nelements();
+    while ( !fiter.pastEnd() ) {
+      Table ftab = fiter.table();
+      ScalarColumn<uInt> freqidCol(ftab, "FREQ_ID");
+      // get the SpectralCoordinate for the freqid, which we are iterating over
+      SpectralCoordinate sC = in->frequencies().getSpectralCoordinate(freqidCol(0));
+      FrequencyAligner<Float> fa( sC, nchan, refEpoch,
+                                  direction, refPos, system );
+      // realign the SpectralCoordinate and put into the output Scantable
+      Vector<String> units(1);
+      units = String("Hz");
+      Bool linear=True;
+      SpectralCoordinate sc2 = fa.alignedSpectralCoordinate(linear);
+      sc2.setWorldAxisUnits(units);
+      uInt id = out->frequencies().addEntry(sc2.referencePixel()[0],
+                                            sc2.referenceValue()[0],
+                                            sc2.increment()[0]);
+      TableVector<uInt> tvec(ftab, "FREQ_ID");
+      tvec = id;
+      // create the "global" abcissa for alignment with same FREQ_ID
+      Vector<Double> abc(nchan);
+      Double w;
+      for (uInt i=0; i<nchan; i++) {
+        sC.toWorld(w,Double(i));
+        abc[i] = w;
+      }
+      // cache abcissa for same time stamps, so iterate over those
+      TableIterator timeiter(ftab, "TIME");
+      while ( !timeiter.pastEnd() ) {
+        Table tab = timeiter.table();
+        ArrayColumn<Float> specCol(tab, "SPECTRA");
+        ArrayColumn<uChar> flagCol(tab, "FLAGTRA");
+        MEpoch::ROScalarColumn timeCol(tab, "TIME");
+        // use align abcissa cache after the first row
+        bool first = true;
+        // these rows should be just be POLNO
+        for (int i=0; i<int(tab.nrow()); ++i) {
+          // input values
+          Vector<uChar> flag = flagCol(i);
+          Vector<Bool> mask(flag.shape());
+          Vector<Float> specOut, spec;
+          spec  = specCol(i);
+          Vector<Bool> maskOut;Vector<uChar> flagOut;
+          convertArray(mask, flag);
+          // alignment
+          Bool ok = fa.align(specOut, maskOut, abc, spec,
+                             mask, timeCol(i), !first,
+                             interp, False);
+          // back into scantable
+          flagOut.resize(maskOut.nelements());
+          convertArray(flagOut, maskOut);
+          flagCol.put(i, flagOut);
+          specCol.put(i, specOut);
+          // start abcissa caching
+          first = false;
+        }
+        // next timestamp
+        ++timeiter;
+      }
+      // next FREQ_ID
+      ++fiter;
+    }
+    // next aligner
+    ++iter;
+  }
+  // set this afterwards to ensure we are doing insitu correctly.
+  out->frequencies().setFrame(system, true);
+  return out;
+}
+
+CountedPtr<Scantable>
+  asap::STMath::convertPolarisation( const CountedPtr<Scantable>& in,
+                                     const std::string & newtype )
+{
+  if (in->npol() != 2 && in->npol() != 4)
+    throw(AipsError("Can only convert two or four polarisations."));
+  if ( in->getPolType() == newtype )
+    throw(AipsError("No need to convert."));
+  if ( ! in->selector_.empty() )
+    throw(AipsError("Can only convert whole scantable. Unset the selection."));
+  bool insitu = insitu_;
+  setInsitu(false);
+  CountedPtr< Scantable > out = getScantable(in, true);
+  setInsitu(insitu);
+  Table& tout = out->table();
+  tout.rwKeywordSet().define("POLTYPE", String(newtype));
+
+  Block<String> cols(4);
+  cols[0] = "SCANNO";
+  cols[1] = "CYCLENO";
+  cols[2] = "BEAMNO";
+  cols[3] = "IFNO";
+  TableIterator it(in->originalTable_, cols);
+  String basetype = in->getPolType();
+  STPol* stpol = STPol::getPolClass(in->factories_, basetype);
+  try {
+    while ( !it.pastEnd() ) {
+      Table tab = it.table();
+      uInt row = tab.rowNumbers()[0];
+      stpol->setSpectra(in->getPolMatrix(row));
+      Float fang,fhand,parang;
+      fang = in->focusTable_.getTotalFeedAngle(in->mfocusidCol_(row));
+      fhand = in->focusTable_.getFeedHand(in->mfocusidCol_(row));
+      parang = in->paraCol_(row);
+      /// @todo re-enable this
+      // disable total feed angle to support paralactifying Caswell style
+      stpol->setPhaseCorrections(parang, -parang, fhand);
+      Int npolout = 0;
+      for (uInt i=0; i<tab.nrow(); ++i) {
+        Vector<Float> outvec = stpol->getSpectrum(i, newtype);
+        if ( outvec.nelements() > 0 ) {
+          tout.addRow();
+          TableCopy::copyRows(tout, tab, tout.nrow()-1, 0, 1);
+          ArrayColumn<Float> sCol(tout,"SPECTRA");
+          ScalarColumn<uInt> pCol(tout,"POLNO");
+          sCol.put(tout.nrow()-1 ,outvec);
+          pCol.put(tout.nrow()-1 ,uInt(npolout));
+          npolout++;
+       }
+      }
+      tout.rwKeywordSet().define("nPol", npolout);
+      ++it;
+    }
+  } catch (AipsError& e) {
+    delete stpol;
+    throw(e);
+  }
+  delete stpol;
+  return out;
+}
+
+CountedPtr< Scantable >
+  asap::STMath::mxExtract( const CountedPtr< Scantable > & in,
+                           const std::string & scantype )
+{
+  bool insitu = insitu_;
+  setInsitu(false);
+  CountedPtr< Scantable > out = getScantable(in, true);
+  setInsitu(insitu);
+  Table& tout = out->table();
+  std::string taql = "SELECT FROM $1 WHERE BEAMNO != REFBEAMNO";
+  if (scantype == "on") {
+    taql = "SELECT FROM $1 WHERE BEAMNO == REFBEAMNO";
+  }
+  Table tab = tableCommand(taql, in->table());
+  TableCopy::copyRows(tout, tab);
+  if (scantype == "on") {
+    // re-index SCANNO to 0
+    TableVector<uInt> vec(tout, "SCANNO");
+    vec = 0;
+  }
+  return out;
+}
+
+CountedPtr< Scantable >
+  asap::STMath::lagFlag( const CountedPtr< Scantable > & in,
+                          double frequency, int width )
+{
+  CountedPtr< Scantable > out = getScantable(in, false);
+  Table& tout = out->table();
+  TableIterator iter(tout, "FREQ_ID");
+  FFTServer<Float,Complex> ffts;
+  while ( !iter.pastEnd() ) {
+    Table tab = iter.table();
+    Double rp,rv,inc;
+    ROTableRow row(tab);
+    const TableRecord& rec = row.get(0);
+    uInt freqid = rec.asuInt("FREQ_ID");
+    out->frequencies().getEntry(rp, rv, inc, freqid);
+    ArrayColumn<Float> specCol(tab, "SPECTRA");
+    ArrayColumn<uChar> flagCol(tab, "FLAGTRA");
+    for (int i=0; i<int(tab.nrow()); ++i) {
+      Vector<Float> spec = specCol(i);
+      Vector<uChar> flag = flagCol(i);
+      Int lag = Int(spec.nelements()*abs(inc)/frequency);
+      for (int k=0; k < flag.nelements(); ++k ) {
+        if (flag[k] > 0) {
+          spec[k] = 0.0;
+        }
+      }
+      Vector<Complex> lags;
+      ffts.fft(lags, spec);
+      Int start =  max(0, lag-width);
+      Int end =  min(Int(lags.nelements()-1), lag+width);
+      if (start == end) {
+        lags[start] = Complex(0.0);
+      } else {
+        for (int j=start; j <=end ;++j) {
+          lags[j] = Complex(0.0);
+        }
+      }
+      ffts.fft(spec, lags);
+      specCol.put(i, spec);
+    }
+    ++iter;
+  }
+  return out;
+}
Index: tags/Release2.1.0b/src/STMath.h
===================================================================
--- tags/Release2.1.0b/src/STMath.h	(revision 1196)
+++ tags/Release2.1.0b/src/STMath.h	(revision 1196)
@@ -0,0 +1,232 @@
+//
+// C++ Interface: STMath
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPSTMATH_H
+#define ASAPSTMATH_H
+
+#include <string>
+#include <map>
+
+#include <casa/aips.h>
+#include <casa/Utilities/CountedPtr.h>
+#include <casa/BasicSL/String.h>
+#include <casa/Arrays/Vector.h>
+#include <scimath/Mathematics/InterpolateArray1D.h>
+
+#include "Scantable.h"
+#include "STDefs.h"
+#include "STPol.h"
+#include "Logger.h"
+
+namespace asap {
+
+/**
+	* Mathmatical operations on Scantable objects
+	* @author Malte Marquarding
+*/
+class STMath : private Logger {
+public:
+	// typedef for long method name
+  typedef casa::InterpolateArray1D<casa::Double,
+                                   casa::Float>::InterpolationMethod imethod;
+
+  // typedef for std::map
+  typedef std::map<std::string, imethod> imap;
+
+/**
+  * whether to operate on the given Scantable or return a new one
+  * @param insitu the toggle for this behaviour
+  */
+  STMath(bool insitu=true);
+
+  virtual ~STMath();
+
+  /**
+   * get the currnt @attr inistu state
+   */
+  bool insitu() const { return insitu_;};
+
+  /**
+   * set the currnt @attr inistu state
+   * @param b the new state
+   */
+  void setInsitu(bool b) { insitu_ = b; };
+
+
+  /**
+    * average a vector of Scantables
+    * @param in the vector of Scantables to average
+    * @param an optional mask to apply on specific weights
+    * @param weight weighting scheme
+    * @param avmode the mode ov averaging. Per "SCAN" or "ALL".
+    * @return a casa::CountedPtr<Scantable> which either holds a new Scantable
+    * or returns the imput pointer.
+    */
+  casa::CountedPtr<Scantable>
+    average( const std::vector<casa::CountedPtr<Scantable> >& in,
+             const std::vector<bool>& mask = std::vector<bool>(),
+             const std::string& weight = "NONE",
+             const std::string& avmode = "SCAN");
+
+  /**
+    * median average a vector of Scantables. See also STMath::average
+    * @param in the Scantable to average
+    * @param mode the averaging mode. Currently only "MEDIAN"
+    * @param avmode the mode ov averaging. Per "SCAN" or "ALL".
+    * @return a casa::CountedPtr<Scantable> which either holds a new Scantable
+    * or returns the imput pointer.
+    */
+  casa::CountedPtr<Scantable>
+    averageChannel( const casa::CountedPtr<Scantable> & in,
+                    const std::string& mode = "MEDIAN",
+                    const std::string& avmode = "SCAN");
+
+  /**
+    * Average polarisations together. really only useful if only linears are
+    * available.
+    * @param in the input Scantable
+    * @param mask an optional mask if weight allows one
+    * @param weight weighting scheme
+    * @return
+    */
+  casa::CountedPtr< Scantable >
+    averagePolarisations( const casa::CountedPtr< Scantable > & in,
+                          const std::vector<bool>& mask,
+                          const std::string& weight );
+
+  /**
+    * Average beams together.
+    * @param in the input Scantable
+    * @param mask an optional mask if weight allows one
+    * @param weight weighting scheme
+    * @return
+    */
+  casa::CountedPtr< Scantable >
+    averageBeams( const casa::CountedPtr< Scantable > & in,
+                   const std::vector<bool>& mask,
+                   const std::string& weight );
+
+  casa::CountedPtr<Scantable>
+    unaryOperate( const casa::CountedPtr<Scantable>& in, float val,
+                  const std::string& mode, bool tsys=false );
+
+  casa::CountedPtr<Scantable> autoQuotient(const casa::CountedPtr<Scantable>& in,
+                                           const std::string& mode = "NEAREST",
+                                           bool preserve = true);
+
+  casa::CountedPtr<Scantable> quotient( const casa::CountedPtr<Scantable>& on,
+                                        const casa::CountedPtr<Scantable>& off,
+                                        bool preserve = true );
+
+  casa::CountedPtr<Scantable>
+    freqSwitch( const casa::CountedPtr<Scantable>& in );
+
+  std::vector<float> statistic(const casa::CountedPtr<Scantable>& in,
+                               const std::vector<bool>& mask,
+                               const std::string& which);
+
+  casa::CountedPtr<Scantable> bin( const casa::CountedPtr<Scantable>& in,
+                                   int width=5);
+  casa::CountedPtr<Scantable>
+    resample(const casa::CountedPtr<Scantable>& in,
+             const std::string& method, float width);
+
+  casa::CountedPtr<Scantable>
+    smooth(const casa::CountedPtr<Scantable>& in, const std::string& kernel,
+                      float width);
+
+  casa::CountedPtr<Scantable>
+    gainElevation(const casa::CountedPtr<Scantable>& in,
+                  const std::vector<float>& coeff,
+                  const std::string& fileName,
+                  const std::string& method);
+  casa::CountedPtr<Scantable>
+    convertFlux(const casa::CountedPtr<Scantable>& in, float d,
+                float etaap, float jyperk);
+
+  casa::CountedPtr<Scantable> opacity(const casa::CountedPtr<Scantable>& in,
+                                      float tau);
+
+  casa::CountedPtr<Scantable>
+    merge(const std::vector<casa::CountedPtr<Scantable> >& in);
+
+  casa::CountedPtr<Scantable>
+    invertPhase( const casa::CountedPtr<Scantable>& in);
+
+  casa::CountedPtr<Scantable>
+    rotateXYPhase( const casa::CountedPtr<Scantable>& in, float phase);
+
+  casa::CountedPtr<Scantable>
+    rotateLinPolPhase( const casa::CountedPtr<Scantable>& in, float phase);
+
+  casa::CountedPtr<Scantable>
+    swapPolarisations(const casa::CountedPtr<Scantable>& in);
+
+  casa::CountedPtr<Scantable>
+    frequencyAlign( const casa::CountedPtr<Scantable>& in,
+                    const std::string& refTime = "",
+                    const std::string& method = "cubic" );
+
+  casa::CountedPtr<Scantable>
+    convertPolarisation( const casa::CountedPtr<Scantable>& in,
+                         const std::string& newtype);
+
+  casa::CountedPtr<Scantable>
+    mxExtract( const casa::CountedPtr<Scantable>& in,
+               const std::string& srctype = "on");
+
+  /**
+   * "hard" flag the data, this flags everything selected in setSelection()
+   * @param frequency the frequency to remove
+   * @param width the number of lags to flag left to the side of the frequency
+   */
+  casa::CountedPtr<Scantable>
+    lagFlag( const casa::CountedPtr<Scantable>& in, double frequency,
+              int width);
+
+private:
+  casa::CountedPtr<Scantable>  applyToPol( const casa::CountedPtr<Scantable>& in,
+                                           STPol::polOperation fptr,
+                                           casa::Float phase);
+
+  static imethod stringToIMethod(const std::string& in);
+  static WeightType stringToWeight(const std::string& in);
+
+  void scaleByVector(casa::Table& in,
+                     const casa::Vector<casa::Float>& factor,
+                     bool dotsys);
+
+  void scaleFromAsciiTable(casa::Table& in, const std::string& filename,
+                           const std::string& method,
+                           const casa::Vector<casa::Float>& xout,
+                           bool dotsys);
+
+  void scaleFromTable(casa::Table& in, const casa::Table& table,
+                      const std::string& method,
+                      const casa::Vector<casa::Float>& xout, bool dotsys);
+
+  void convertBrightnessUnits(casa::CountedPtr<Scantable>& in,
+                              bool tokelvin, float cfac);
+
+  casa::CountedPtr< Scantable >
+    getScantable(const casa::CountedPtr< Scantable >& in, bool droprows);
+
+  casa::MaskedArray<casa::Float>
+    maskedArray( const casa::Vector<casa::Float>& s,
+                 const casa::Vector<casa::uChar>& f );
+  casa::Vector<casa::uChar>
+    flagsFromMA(const casa::MaskedArray<casa::Float>& ma);
+
+  bool insitu_;
+};
+
+}
+#endif
Index: tags/Release2.1.0b/src/STMathWrapper.h
===================================================================
--- tags/Release2.1.0b/src/STMathWrapper.h	(revision 1196)
+++ tags/Release2.1.0b/src/STMathWrapper.h	(revision 1196)
@@ -0,0 +1,166 @@
+//
+// C++ Interface: STMathWrapper
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <Malte.Marquarding@csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPSTMATHWRAPPER_H
+#define ASAPSTMATHWRAPPER_H
+
+#include <vector>
+#include <string>
+
+#include <casa/Utilities/CountedPtr.h>
+
+#include "STMath.h"
+#include "Scantable.h"
+#include "ScantableWrapper.h"
+
+namespace asap {
+
+/**
+Wrapper class to handle ScantableWrapper
+
+@author Malte Marquarding
+*/
+class STMathWrapper : public STMath {
+public:
+  STMathWrapper() {;}
+  STMathWrapper(bool insitu) : STMath(insitu) {;}
+
+  virtual ~STMathWrapper() {;}
+
+  ScantableWrapper
+    average( const std::vector<ScantableWrapper>& in,
+             const std::vector<bool>& mask,
+             const std::string& weight,
+             const std::string& avmode )
+  {
+    std::vector<casa::CountedPtr<Scantable> > sts;
+    for (unsigned int i=0; i<in.size(); ++i) sts.push_back(in[i].getCP());
+    return ScantableWrapper(STMath::average(sts, mask, weight, avmode));
+  }
+
+  ScantableWrapper
+    averageChannel( const ScantableWrapper& in,
+                    const std::string& mode = "MEDIAN",
+                    const std::string& avmode = "NONE")
+  {
+    return ScantableWrapper(STMath::averageChannel(in.getCP(), mode, avmode));
+  }
+
+  ScantableWrapper
+    averagePolarisations( const ScantableWrapper& in,
+                          const std::vector<bool>& mask,
+                          const std::string& weight)
+  { return ScantableWrapper(STMath::averagePolarisations(in.getCP(),mask, weight));}
+
+  ScantableWrapper
+    averageBeams( const ScantableWrapper& in,
+                  const std::vector<bool>& mask,
+                  const std::string& weight)
+
+  { return ScantableWrapper(STMath::averageBeams(in.getCP(),mask, weight));}
+
+  ScantableWrapper
+    unaryOperate( const ScantableWrapper& in, float val,
+                  const std::string& mode, bool tsys=false )
+  { return ScantableWrapper(STMath::unaryOperate(in.getCP(), val, mode, tsys)); }
+
+  ScantableWrapper autoQuotient( const ScantableWrapper& in,
+                                 const std::string& mode = "NEAREST",
+                                 bool preserve = true )
+  { return ScantableWrapper(STMath::autoQuotient(in.getCP(), mode, preserve)); }
+
+  ScantableWrapper quotient( const ScantableWrapper& on,
+                             const ScantableWrapper& off,
+                             bool preserve = true )
+  { return ScantableWrapper( STMath::quotient( on.getCP(), off.getCP(),
+                                               preserve ) ); }
+
+  ScantableWrapper
+    freqSwitch( const ScantableWrapper& in )
+  { return ScantableWrapper(STMath::freqSwitch(in.getCP())); }
+
+  std::vector<float> statistic(const ScantableWrapper& in,
+                               const std::vector<bool>& mask,
+                               const std::string& which)
+  { return STMath::statistic(in.getCP(), mask, which); }
+
+  ScantableWrapper bin( const ScantableWrapper& in, int width=5)
+  { return ScantableWrapper(STMath::bin(in.getCP(), width)); }
+
+  ScantableWrapper
+    resample(const ScantableWrapper& in,
+             const std::string& method, float width)
+  { return ScantableWrapper(STMath::resample(in.getCP(), method, width)); }
+
+  ScantableWrapper
+    smooth(const ScantableWrapper& in, const std::string& kernel, float width)
+  { return ScantableWrapper(STMath::smooth(in.getCP(), kernel, width)); }
+
+  ScantableWrapper
+    gainElevation(const ScantableWrapper& in,
+                  const std::vector<float>& coeff,
+                  const std::string& filename,
+                  const std::string& method)
+
+  { return
+      ScantableWrapper(STMath::gainElevation(in.getCP(), coeff, filename, method)); }
+
+  ScantableWrapper
+    convertFlux(const ScantableWrapper& in, float d,
+                float etaap, float jyperk)
+  { return ScantableWrapper(STMath::convertFlux(in.getCP(), d, etaap, jyperk)); }
+
+  ScantableWrapper opacity(const ScantableWrapper& in,
+                                      float tau)
+  { return ScantableWrapper(STMath::opacity(in.getCP(), tau)); }
+
+  ScantableWrapper
+    merge(const std::vector<ScantableWrapper >& in)
+
+  {
+    std::vector<casa::CountedPtr<Scantable> > sts;
+    for (unsigned int i=0; i<in.size(); ++i) sts.push_back(in[i].getCP());
+    return ScantableWrapper(STMath::merge(sts)); }
+
+  ScantableWrapper rotateXYPhase( const ScantableWrapper& in, float angle)
+  { return ScantableWrapper(STMath::rotateXYPhase(in.getCP(), angle)); }
+
+  ScantableWrapper rotateLinPolPhase( const ScantableWrapper& in, float angle)
+  { return ScantableWrapper(STMath::rotateLinPolPhase(in.getCP(), angle)); }
+
+  ScantableWrapper invertPhase( const ScantableWrapper& in )
+  { return ScantableWrapper(STMath::invertPhase(in.getCP())); }
+
+  ScantableWrapper swapPolarisations( const ScantableWrapper& in )
+  { return ScantableWrapper(STMath::swapPolarisations(in.getCP())); }
+
+  ScantableWrapper frequencyAlign( const ScantableWrapper& in,
+                                   const std::string& refTime,
+                                   const std::string& method  )
+  { return ScantableWrapper(STMath::frequencyAlign(in.getCP())); }
+
+  ScantableWrapper convertPolarisation( const ScantableWrapper& in,
+                                        const std::string& newtype )
+  { return ScantableWrapper(STMath::convertPolarisation(in.getCP(),newtype)); }
+
+  ScantableWrapper mxExtract( const ScantableWrapper& in,
+                              const std::string& scantype="on" )
+  { return ScantableWrapper(STMath::mxExtract(in.getCP(),scantype)); }
+
+  ScantableWrapper lagFlag( const ScantableWrapper& in,
+                            double frequency, int width )
+  { return ScantableWrapper(STMath::lagFlag(in.getCP(), frequency, width)); }
+
+};
+
+}
+
+#endif
Index: tags/Release2.1.0b/src/STMolecules.cpp
===================================================================
--- tags/Release2.1.0b/src/STMolecules.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STMolecules.cpp	(revision 1196)
@@ -0,0 +1,133 @@
+//
+// C++ Implementation: STMolecules
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#include <casa/Exceptions/Error.h>
+#include <tables/Tables/TableDesc.h>
+#include <tables/Tables/SetupNewTab.h>
+#include <tables/Tables/ScaColDesc.h>
+#include <tables/Tables/TableRecord.h>
+#include <tables/Tables/TableParse.h>
+#include <tables/Tables/TableRow.h>
+#include <casa/Containers/RecordField.h>
+
+#include "STMolecules.h"
+
+
+using namespace casa;
+
+namespace asap {
+
+const casa::String STMolecules::name_ = "MOLECULES";
+
+STMolecules::STMolecules(const Scantable& parent) :
+  STSubTable( parent, name_ )
+{
+  setup();
+}
+
+asap::STMolecules::STMolecules( casa::Table tab ) : STSubTable(tab, name_)
+{
+  restfreqCol_.attach(table_,"RESTFREQUENCY");
+  nameCol_.attach(table_,"NAME");
+  formattednameCol_.attach(table_,"FORMATTEDNAME");
+}
+
+STMolecules::~STMolecules()
+{
+}
+
+STMolecules & asap::STMolecules::operator =( const STMolecules & other )
+{
+  if ( this != &other ) {
+    static_cast<STSubTable&>(*this) = other;
+    restfreqCol_.attach(table_,"RESTFREQUENCY");
+    nameCol_.attach(table_,"NAME");
+    formattednameCol_.attach(table_,"FORMATTEDNAME");
+  }
+  return *this;
+}
+
+void asap::STMolecules::setup( )
+{
+  // add to base class table
+  table_.addColumn(ScalarColumnDesc<Double>("RESTFREQUENCY"));
+  table_.addColumn(ScalarColumnDesc<String>("NAME"));
+  table_.addColumn(ScalarColumnDesc<String>("FORMATTEDNAME"));
+  table_.rwKeywordSet().define("UNIT", String("Hz"));
+  // new cached columns
+  restfreqCol_.attach(table_,"RESTFREQUENCY");
+  nameCol_.attach(table_,"NAME");
+  formattednameCol_.attach(table_,"FORMATTEDNAME");
+}
+
+uInt STMolecules::addEntry( Double restfreq, const String& name,
+                            const String& formattedname )
+{
+
+  Table result =
+    table_( near(table_.col("RESTFREQUENCY"), restfreq) );
+  uInt resultid = 0;
+  if ( result.nrow() > 0) {
+    ROScalarColumn<uInt> c(result, "ID");
+    c.get(0, resultid);
+  } else {
+    uInt rno = table_.nrow();
+    table_.addRow();
+    // get last assigned _id and increment
+    if ( rno > 0 ) {
+      idCol_.get(rno-1, resultid);
+      resultid++;
+    }
+    restfreqCol_.put(rno, restfreq);
+    nameCol_.put(rno, name);
+    formattednameCol_.put(rno, formattedname);
+    idCol_.put(rno, resultid);
+  }
+  return resultid;
+}
+
+void STMolecules::getEntry( Double& restfreq, String& name,
+                            String& formattedname, uInt id ) const
+{
+  Table t = table_(table_.col("ID") == Int(id) );
+  if (t.nrow() == 0 ) {
+    throw(AipsError("STMolecules::getEntry - id out of range"));
+  }
+  ROTableRow row(t);
+  // get first row - there should only be one matching id
+  const TableRecord& rec = row.get(0);
+  restfreq = rec.asDouble("RESTFREQUENCY");
+  name = rec.asString("NAME");
+  formattedname = rec.asString("FORMATTEDNAME");
+}
+
+std::vector< double > asap::STMolecules::getRestFrequencies( ) const
+{
+  std::vector<double> out;
+  Vector<Double> rfs = restfreqCol_.getColumn();
+  rfs.tovector(out);
+  return out;
+}
+
+double asap::STMolecules::getRestFrequency( uInt id ) const
+{
+  Table t = table_(table_.col("ID") == Int(id) );
+  if (t.nrow() == 0 ) {
+    throw(AipsError("STMolecules::getRestFrequency - id out of range"));
+  }
+  ROTableRow row(t);
+  const TableRecord& rec = row.get(0);
+  return double(rec.asDouble("RESTFREQUENCY"));
+}
+
+
+}//namespace
+
Index: tags/Release2.1.0b/src/STMolecules.h
===================================================================
--- tags/Release2.1.0b/src/STMolecules.h	(revision 1196)
+++ tags/Release2.1.0b/src/STMolecules.h	(revision 1196)
@@ -0,0 +1,62 @@
+//
+// C++ Interface: STMolecules
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPSTMOLECULES_H
+#define ASAPSTMOLECULES_H
+
+#include <casa/aips.h>
+#include <casa/BasicSL/String.h>
+#include <tables/Tables/Table.h>
+#include <tables/Tables/ScalarColumn.h>
+
+#include "STSubTable.h"
+
+namespace asap {
+
+/**
+The Molecules subtable of the Scantable
+
+@author Malte Marquarding
+*/
+class STMolecules : public STSubTable {
+public:
+  STMolecules() {;}
+  STMolecules(casa::Table tab);
+  STMolecules( const Scantable& parent);
+
+  virtual ~STMolecules();
+
+  STMolecules& operator=(const STMolecules& other);
+
+  casa::uInt addEntry( casa::Double restfreq, const casa::String& name="",
+                       const casa::String& formattedname="");
+
+  void getEntry( casa::Double& restfreq, casa::String& name,
+                 casa::String& formattedname, casa::uInt id) const;
+
+  std::vector<double> getRestFrequencies() const;
+  double getRestFrequency( casa::uInt id ) const;
+  const casa::String& name() const { return name_; }
+
+private:
+  void setup();
+  static const casa::String name_;
+  //casa::Table table_;
+  //casa::ScalarColumn<casa::uInt> freqidCol_;
+  casa::ScalarColumn<casa::Double> restfreqCol_;
+  casa::ScalarColumn<casa::String> nameCol_;
+  casa::ScalarColumn<casa::String> formattednameCol_; // e.g. latex
+
+};
+
+}
+
+#endif
Index: tags/Release2.1.0b/src/STPol.cpp
===================================================================
--- tags/Release2.1.0b/src/STPol.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STPol.cpp	(revision 1196)
@@ -0,0 +1,101 @@
+//
+// C++ Implementation: STPol
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2005
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+
+#include <casa/Exceptions/Error.h>
+#include "STPol.h"
+
+std::map<std::string, std::pair<int, std::string> > asap::STPol::polmap_;
+std::map<std::string, std::map<int, std::string> > asap::STPol::labelmap_;
+
+void asap::STPol::initLabelMap()
+{
+  if ( labelmap_.empty() ) {
+    std::map<int, std::string> linears;
+    linears[0] = "XX";linears[1] = "YY";
+    linears[2] = "Re(XY)";linears[3] = "Imag(XY)";
+    STPol::labelmap_["linear"] = linears;
+    std::map<int, std::string> circulars;
+    circulars[0] = "RR";circulars[1] = "LL";
+    circulars[2] = "Re(RL)";circulars[3] = "Imag(RL)";
+    STPol::labelmap_["circular"] = circulars;
+    std::map<int, std::string> stokes;
+    stokes[0]="I";stokes[1]="Q";stokes[2]="U";stokes[3]="V";
+    STPol::labelmap_["stokes"] = stokes;
+    std::map<int, std::string> linpol;
+    linpol[1] = "Plinear";linpol[2] = "Pangle";
+    STPol::labelmap_["linpol"] = linpol;
+  }
+}
+
+std::string asap::STPol::getPolLabel( int index,
+                                      const std::string& type)
+{
+  initLabelMap();
+  if ( labelmap_.find(type) ==  labelmap_.end() ) {
+    std::string msg = "Illegal polarisation type "+type;
+    throw(casa::AipsError(msg));
+  } else {
+    std::map<int, std::string>  poltype = labelmap_[type];
+    if ( poltype.find(index) ==  poltype.end() ) {
+      std::string msg = "Illegal polarisation index";
+      throw(casa::AipsError(msg));
+    } else {
+      return poltype[index];
+    }
+  }
+}
+
+void asap::STPol::initPolMap( )
+{
+  if ( polmap_.empty() ) {
+    std::pair<int, std::string> val;
+    val.first = 0; val.second = "linear";
+    STPol::polmap_["XX"] = val;
+    val.first = 1;
+    STPol::polmap_["YY"] = val;
+    val.first = 2;
+    STPol::polmap_["Re(XY)"] = val;
+    val.first = 3;
+    STPol::polmap_["Imag(XY)"] = val;
+    val.first = 0; val.second = "stokes";
+    STPol::polmap_["I"] = val;
+    val.first = 1;
+    STPol::polmap_["Q"] = val;
+    val.first = 2;
+    STPol::polmap_["U"] = val;
+    val.first = 3;
+    STPol::polmap_["V"] = val;
+    val.first = 1; val.second = "linpol";
+    STPol::polmap_["Plinear"] = val;
+    val.first = 2;
+    STPol::polmap_["Pangle"] = val;
+    val.first = 0; val.second = "circular";
+    STPol::polmap_["RR"] = val;
+    val.first = 1;
+    STPol::polmap_["LL"] = val;
+    val.first = 2;
+    STPol::polmap_["Re(RL)"] = val;
+    val.first = 3;
+    STPol::polmap_["Imag(RL)"] = val;
+  }
+}
+
+std::pair<int, std::string> asap::STPol::polFromString( const std::string& key )
+{
+  initPolMap();
+  if ( polmap_.find(key) ==  polmap_.end() ) {
+    std::string msg = "Illegal polarisation type "+key;
+    throw(casa::AipsError(msg));
+  } else {
+    return polmap_[key];
+  }
+}
Index: tags/Release2.1.0b/src/STPol.h
===================================================================
--- tags/Release2.1.0b/src/STPol.h	(revision 1196)
+++ tags/Release2.1.0b/src/STPol.h	(revision 1196)
@@ -0,0 +1,109 @@
+//
+// C++ Interface: STPol
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <Malte.Marquarding@csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPSTPOL_H
+#define ASAPSTPOL_H
+
+#include <map>
+#include <utility>
+
+#include <casa/aips.h>
+#include <casa/Exceptions/Error.h>
+#include <casa/Arrays/Matrix.h>
+#include <casa/Arrays/Vector.h>
+#include "Factory.h"
+
+namespace asap {
+
+/**
+Convert betweeen the possible polarisations (linear, circular, stokes, stokes2)
+
+@author Malte Marquarding
+@date $Date:$
+
+*/
+class STPol {
+public:
+
+  typedef  void (STPol::*polOperation)( casa::Float phase );
+  STPol(): totalfeed_(0.0),parangle_(0.0),feedhand_(1.0) {}
+  virtual ~STPol() {}
+
+  typedef FactoryBase<STPol> STPolFactory;
+
+  static STPol* getPolClass( std::map<std::string,STPol::STPolFactory *> factories,
+                             const std::string& type )
+    { return factories[type]->create(); }
+
+  casa::Vector<casa::Float> getSpectrum( casa::uInt index, const std::string& mode )
+    {
+      if (mode == "linear")
+        return getLinear(index);
+      else if ( mode == "stokes" )
+        return getStokes(index);
+      else if ( mode == "linpol" )
+        return getLinPol(index);
+      else if ( mode == "circular" )
+        return getCircular(index);
+      else
+        throw(casa::AipsError("Polarisation type unknown"));
+    }
+
+  virtual casa::Vector<casa::Float> getCircular( casa::uInt index ) = 0;
+
+  virtual casa::Vector<casa::Float> getStokes( casa::uInt index ) = 0;
+
+  virtual casa::Vector<casa::Float> getLinPol( casa::uInt index ) = 0;
+
+  virtual casa::Vector<casa::Float> getLinear( casa::uInt index ) = 0;
+
+  virtual void rotatePhase( casa::Float phase ) {}
+  virtual void rotateLinPolPhase( casa::Float phase ) {}
+
+  virtual void invertPhase( casa::Float phase ) {}
+
+  casa::uInt nspec() const { return basespectra_.ncolumn(); }
+
+  const casa::Vector<casa::Float> getSpectrum(casa::uInt index) const
+    { return basespectra_.column(index); }
+
+  casa::Matrix<casa::Float>& getSpectra()
+    { return basespectra_; }
+
+  void setSpectra(const casa::Matrix<casa::Float>& spec)
+    { basespectra_.resize(); basespectra_ = spec; }
+
+
+  void setPhaseCorrections(casa::Float parangle=0.0, casa::Float totalfeed=0.0,
+                           casa::Float feedhand=1.0)
+    { totalfeed_=totalfeed;parangle_=parangle;feedhand_=feedhand;}
+
+  casa::Float getTotalPhase() const { return totalfeed_+parangle_; }
+  casa::Float getFeedHand() const { return feedhand_; }
+
+  static std::pair<int, std::string> polFromString(const std::string& key);
+  static std::string getPolLabel(int index, const std::string& type = "linear");
+
+private:
+  static void initPolMap();
+  static void initLabelMap();
+  static std::map<std::string, std::pair<int, std::string> > polmap_;
+  static std::map<std::string, std::map<int, std::string> > labelmap_;
+
+  casa::Float totalfeed_,parangle_,feedhand_;
+  std::string mode_;
+  casa::Matrix<casa::Float> basespectra_;
+
+};
+
+}
+
+#endif
Index: tags/Release2.1.0b/src/STPolCircular.cpp
===================================================================
--- tags/Release2.1.0b/src/STPolCircular.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STPolCircular.cpp	(revision 1196)
@@ -0,0 +1,99 @@
+//
+// C++ Implementation: STPolCircular
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <Malte.Marquarding@csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+
+#include <casa/Arrays/ArrayMath.h>
+#include <casa/BasicMath/Math.h>
+#include <casa/Exceptions/Error.h>
+#include <casa/BasicSL/Constants.h>
+#include "STPolCircular.h"
+
+using namespace casa;
+
+namespace asap {
+
+Factory<STPol,STPolCircular> STPolCircular::myFactory;
+
+STPolCircular::~STPolCircular()
+{
+}
+
+
+Vector<Float> asap::STPolCircular::getStokes( uint index )
+{
+  /// @todo implement full stokes support when circular cross pols are available
+  Vector<Float> out;
+  if (nspec() == 2) {
+    if ( index == 0 )
+      out = Vector<Float>(getSpectrum(0) + getSpectrum(1));
+  }
+  return out;
+}
+
+Vector<Float> asap::STPolCircular::getLinPol( uInt index )
+{
+  if (nspec() != 4) {
+    throw(AipsError("You must have 4 circular polarisations to run this function"));
+  }
+  if ( index < 0 || index >4 ) throw(AipsError("LinPol index out of range"));
+  Vector<Float> out,q,u;
+  if ( nspec() == 4) {
+    switch(index) {
+      case 1:
+        q = getStokes(1);
+        u = getStokes(2);
+        out = Vector<Float>(sqrt(pow(q,Float(2.0))+pow(u, Float(2.0))));
+        break;
+      case 2:
+        q = getStokes(1);
+        u = getStokes(2);
+        out = Vector<Float>(Float(180.0/C::pi/2.0) * atan2(u,q));
+        break;
+      default:
+        out = getStokes(index);
+    }
+  }
+  return out;
+}
+
+Vector<Float> asap::STPolCircular::getCircular(uInt index )
+{
+  return getSpectrum(index);
+}
+
+Vector<Float> asap::STPolCircular::getLinear(uInt index )
+{
+  // convert to stokes I/ V first
+  //
+  //   We use the convention
+  //    I = (XX+YY)  // definition changed
+
+  if (nspec() != 4) {
+    throw(AipsError("You must have 4 circular polarisations to run this function"));
+  }
+  if ( index == 2 || index ==3  ) throw(AipsError("Re/Imag XY not implemented"));
+  Vector<Float> I,V,out;
+  I = getStokes(0);
+  V = getStokes(3);
+  switch(index) {
+  case 0:
+    out = (I + V)/Float(2.0);
+    break;
+  case 1:
+    out = (I - V)/Float(2.0);
+    break;
+  default:
+    out = Vector<Float>();
+  }
+  return out;
+}
+
+}
Index: tags/Release2.1.0b/src/STPolCircular.h
===================================================================
--- tags/Release2.1.0b/src/STPolCircular.h	(revision 1196)
+++ tags/Release2.1.0b/src/STPolCircular.h	(revision 1196)
@@ -0,0 +1,61 @@
+//
+// C++ Interface: STPolLinear
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <Malte.Marquarding@csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPSTPOLCircular_H
+#define ASAPSTPOLCircular_H
+
+#include "Factory.h"
+#include "STPol.h"
+
+namespace asap {
+
+/**
+  The Circular representation of polarisation.
+  NOTE  U and V are probably wrong
+  We are using the following convention:
+  @li I = RR + LL
+  @li Q = RR - LL
+  @li U = 2*Real(RL)
+  @li V = 2*Imag(RL)
+
+  resulting in:
+  @li I' = I
+  @li Q' = Q * cos(theta) - V *sin(theta)
+  @li U' = Q * sin(theta) + U * cos(theta)
+  @li V' = V
+  @author Malte Marquarding
+
+*/
+class STPolCircular : public STPol
+{
+public:
+  STPolCircular() {}
+
+  STPolCircular(const casa::Matrix<casa::Float>& specs)
+    { setSpectra(specs); }
+
+  ~STPolCircular();
+
+  static Factory<STPol,STPolCircular> myFactory;
+
+  virtual casa::Vector<casa::Float> getCircular( casa::uInt index );
+
+  virtual casa::Vector<casa::Float> getStokes( casa::uInt index);
+
+  virtual casa::Vector<casa::Float> getLinPol( casa::uInt index);
+
+  virtual casa::Vector<casa::Float> getLinear( casa::uInt index );
+
+};
+
+}
+
+#endif
Index: tags/Release2.1.0b/src/STPolLinear.cpp
===================================================================
--- tags/Release2.1.0b/src/STPolLinear.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STPolLinear.cpp	(revision 1196)
@@ -0,0 +1,168 @@
+//
+// C++ Implementation: STPolLinear
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <Malte.Marquarding@csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+
+#include <casa/Arrays/ArrayMath.h>
+#include <casa/BasicMath/Math.h>
+#include <casa/Exceptions/Error.h>
+#include <casa/BasicSL/Constants.h>
+#include "STPolLinear.h"
+
+using namespace casa;
+
+namespace asap {
+
+Factory<STPol,STPolLinear> STPolLinear::myFactory;
+
+STPolLinear::~STPolLinear()
+{
+}
+
+
+Vector<Float> asap::STPolLinear::getStokes( uint index )
+{
+  if ( index < 0 || index >4 ) throw(AipsError("Stokes index out of range"));
+  Vector<Float> out;
+  Float phase = getTotalPhase();
+  Vector<Float> q(getSpectrum(0) - getSpectrum(1));
+  if ( nspec() == 4 ) {
+    switch(index) {
+      case 0:
+        out = Vector<Float>(getSpectrum(0) + getSpectrum(1));
+        break;
+      case 1:
+        out = Vector<Float>(q * cos(phase) - Float(2.0)*getSpectrum(2) * sin(phase));
+        break;
+      case 2:
+        out = Vector<Float>(q * sin(phase) + Float(2.0)*getSpectrum(2) * cos(phase));
+        break;
+      case 3:
+        out = getFeedHand() * Float(2.0) * Vector<Float>(getSpectrum(3));
+        break;
+    }
+  } else if (nspec() == 2) {
+    if ( index == 0 )
+      out = Vector<Float>(getSpectrum(0) + getSpectrum(1));
+  }
+  return out;
+}
+
+Vector<Float> asap::STPolLinear::getLinPol( uInt index )
+{
+  if (nspec() != 4) {
+    throw(AipsError("You must have 4 linear polarizations to run this function"));
+  }
+  if ( index < 0 || index >4 ) throw(AipsError("LinPol index out of range"));
+  Vector<Float> out,q,u;
+  if ( nspec() == 4) {
+    switch(index) {
+      case 1:
+        q = getStokes(1);
+        u = getStokes(2);
+        out = Vector<Float>(sqrt(pow(q,Float(2.0))+pow(u, Float(2.0))));
+        break;
+      case 2:
+        q = getStokes(1);
+        u = getStokes(2);
+        out = Vector<Float>(Float(180.0/C::pi/2.0) * atan2(u,q));
+        break;
+      default:
+        out = getStokes(index);
+    }
+  }
+  return out;
+}
+
+Vector<Float> asap::STPolLinear::getLinear(uInt index )
+{
+  return getSpectrum(index);
+}
+
+Vector<Float> asap::STPolLinear::getCircular(uInt index )
+{
+  // convert to stokes I/ V first
+  //
+  //   We use the convention
+  //    I = (RR+LL)  // definition changed
+  if (nspec() != 4) {
+    throw(AipsError("You must have 4 linear polarizations to run this function"));
+  }
+  if ( index == 2 || index ==3  ) throw(AipsError("Re/Imag RL not implemented"));
+  Vector<Float> I,V,out;
+  I = getStokes(0);
+  V = getStokes(3);
+  switch(index) {
+  case 0:
+    out = (I + V)/Float(2.0);
+    break;
+  case 1:
+    out = (I - V)/Float(2.0);
+    break;
+  default:
+    out = Vector<Float>();
+  }
+  return out;
+}
+
+void asap::STPolLinear::rotatePhase( Float phase )
+{
+  if (nspec() != 4) {
+    throw(AipsError("You must have 4 linear polarizations to run this function"));
+  }
+  Float cosVal = cos(C::pi/180.0*phase);
+  Float sinVal = sin(C::pi/180.0*phase);
+  Matrix<Float>& specs = getSpectra();
+  Vector<Float> R2 = specs.column(2)*cosVal - specs.column(3)*sinVal;
+  specs.column(3) =  specs.column(2)*sinVal + specs.column(3)*cosVal;
+  specs.column(2) = R2;
+}
+
+void asap::STPolLinear::invertPhase( Float phase )
+{
+  // phase isnt used, just ro keep interface the same for all pol operations
+  if (nspec() != 4) {
+    throw(AipsError("You must have 4 linear polarizations to run this function"));
+  }
+  Matrix<Float>& specs = getSpectra();
+  Vector<Float> I = specs.column(3);
+  I *= Float(-1.0);
+}
+
+void asap::STPolLinear::rotateLinPolPhase( casa::Float phase )
+{
+//
+// Rotate P = Q + iU but do it directly on the  linear
+// correlations.
+//
+// We are using I=(XX+YY)/2 convention
+// C1 = XX; C2 = YY, C3 = Real(XY)
+//
+  if (nspec() != 4) {
+    throw(AipsError("You must have 4 linear polarizations to run this function"));
+  }
+  Vector<Float> I,Q,U;
+  I = getStokes(0);
+  Q = getStokes(1);
+  U = getStokes(2);
+  // Rotate Q & U (factor of 2 for polarization)
+  Float cosVal = cos(C::pi/180.0*2.0*phase);
+  Float sinVal = sin(C::pi/180.0*2.0*phase);
+  Vector<Float> Q2 = Q*cosVal - U*sinVal;
+  U =  Q*sinVal + U*cosVal;
+  Q = Q2;
+  Matrix<Float>& specs = getSpectra();
+  specs.column(0) = (I+Q)/Float(2.0);
+  specs.column(1) = (I-Q)/Float(2.0);
+  specs.column(2) = U/Float(2.0);
+
+}
+
+}
Index: tags/Release2.1.0b/src/STPolLinear.h
===================================================================
--- tags/Release2.1.0b/src/STPolLinear.h	(revision 1196)
+++ tags/Release2.1.0b/src/STPolLinear.h	(revision 1196)
@@ -0,0 +1,65 @@
+//
+// C++ Interface: STPolLinear
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <Malte.Marquarding@csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPSTPOLLINEAR_H
+#define ASAPSTPOLLINEAR_H
+
+#include "Factory.h"
+#include "STPol.h"
+
+namespace asap {
+
+/**
+  The linear representation of polarisation.
+  We are using the following convention:
+  @li I = XX + YY
+  @li Q = XX - YY
+  @li U = 2*Real(XY)
+  @li V = 2*Imag(XY)
+
+  resulting in:
+  @li I' = I
+  @li Q' = Q * cos(theta) - V *sin(theta)
+  @li U' = Q * sin(theta) + U * cos(theta)
+  @li V' = V
+  @author Malte Marquarding
+
+*/
+class STPolLinear : public STPol
+{
+public:
+  STPolLinear() {}
+
+  STPolLinear(const casa::Matrix<casa::Float>& specs)
+    { setSpectra(specs); }
+
+  ~STPolLinear();
+
+  static Factory<STPol,STPolLinear> myFactory;
+
+  virtual casa::Vector<casa::Float> getCircular( casa::uInt index );
+
+  virtual casa::Vector<casa::Float> getStokes( casa::uInt index);
+
+  virtual casa::Vector<casa::Float> getLinPol( casa::uInt index);
+
+  virtual casa::Vector<casa::Float> getLinear( casa::uInt index );
+
+  virtual void rotatePhase( casa::Float phase );
+  virtual void rotateLinPolPhase( casa::Float phase );
+
+  virtual void invertPhase( casa::Float phase );
+
+};
+
+}
+
+#endif
Index: tags/Release2.1.0b/src/STPolStokes.cpp
===================================================================
--- tags/Release2.1.0b/src/STPolStokes.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STPolStokes.cpp	(revision 1196)
@@ -0,0 +1,101 @@
+//
+// C++ Implementation: STPolStokes
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <Malte.Marquarding@csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+
+#include <casa/Arrays/ArrayMath.h>
+#include <casa/BasicMath/Math.h>
+#include <casa/Exceptions/Error.h>
+#include <casa/BasicSL/Constants.h>
+#include "STPolStokes.h"
+
+using namespace casa;
+
+namespace asap {
+
+Factory<STPol,STPolStokes> STPolStokes::myFactory;
+
+STPolStokes::~STPolStokes()
+{
+}
+
+
+Vector<Float> asap::STPolStokes::getStokes( uint index )
+{
+  return getSpectrum(index);
+}
+
+Vector<Float> asap::STPolStokes::getLinPol( uInt index )
+{
+  if ( index < 0 || index >4 ) throw(AipsError("LinPol index out of range"));
+  Vector<Float> out;
+  if ( nspec() == 4) {
+    switch(index) {
+      case 1:
+        out = Vector<Float>(sqrt(pow(getSpectrum(1),Float(2.0))
+                                 +pow(getSpectrum(2), Float(2.0))));
+        break;
+      case 2:
+        out = Vector<Float>(Float(180.0/C::pi/2.0)
+                            * atan2(getSpectrum(2),getSpectrum(1)));
+        break;
+      default:
+        out = getSpectrum(index);
+    }
+  }
+  return out;
+}
+
+Vector<Float> asap::STPolStokes::getLinear(uInt index )
+{
+  Vector<Float> out;
+  switch(index) {
+  case 0:
+    out = (getSpectrum(0)+getSpectrum(1))/Float(2.0);
+    break;
+  case 1:
+    out = (getSpectrum(0)-getSpectrum(1))/Float(2.0);
+    break;
+  case 2:
+    out = getSpectrum(2)/Float(2.0);
+    break;
+  case 3:
+    out = getSpectrum(3)/Float(2.0);
+    break;
+  default:
+    out = Vector<Float>();
+  }
+  return out;
+
+}
+
+Vector<Float> asap::STPolStokes::getCircular(uInt index )
+{
+  // convert to stokes I/ V first
+  //
+  //   We use the convention
+  //    I = (RR+LL)  // definition changed
+
+  if ( index == 2 || index ==3  ) throw(AipsError("Re/Imag RL not implemented"));
+  Vector<Float> out;
+  switch(index) {
+  case 0:
+    out = (getSpectrum(0) + getSpectrum(3))/Float(2.0);
+    break;
+  case 1:
+    out = (getSpectrum(0) - getSpectrum(3))/Float(2.0);
+    break;
+  default:
+    out = Vector<Float>();
+  }
+  return out;
+}
+
+}
Index: tags/Release2.1.0b/src/STPolStokes.h
===================================================================
--- tags/Release2.1.0b/src/STPolStokes.h	(revision 1196)
+++ tags/Release2.1.0b/src/STPolStokes.h	(revision 1196)
@@ -0,0 +1,53 @@
+//
+// C++ Interface: STPolStokes
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <Malte.Marquarding@csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPSTPOLSTOKES_H
+#define ASAPSTPOLSTOKES_H
+
+#include "Factory.h"
+#include "STPol.h"
+
+namespace asap {
+
+/**
+The stokes representation of polarisation
+
+@author Malte Marquarding
+*/
+class STPolStokes : public STPol
+{
+public:
+  STPolStokes() {}
+
+  STPolStokes(const casa::Matrix<casa::Float>& specs)
+    { setSpectra(specs); }
+
+  ~STPolStokes();
+
+  static Factory<STPol,STPolStokes> myFactory;
+
+  virtual casa::Vector<casa::Float> getCircular( casa::uInt index );
+
+  virtual casa::Vector<casa::Float> getStokes( casa::uInt index);
+
+  virtual casa::Vector<casa::Float> getLinPol( casa::uInt index);
+
+  virtual casa::Vector<casa::Float> getLinear( casa::uInt index );
+
+  //virtual void rotatePhase( casa::Float phase );
+  //virtual void rotateLinPolPhase( casa::Float phase );
+  //virtual void invertPhase( casa::Float phase );
+
+};
+
+}
+
+#endif
Index: tags/Release2.1.0b/src/STSelector.cpp
===================================================================
--- tags/Release2.1.0b/src/STSelector.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STSelector.cpp	(revision 1196)
@@ -0,0 +1,272 @@
+//
+// C++ Implementation: STSelector
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#include <tables/Tables/ExprNode.h>
+#include <tables/Tables/TableParse.h>
+#include <tables/Tables/ExprNode.h>
+#include <casa/BasicSL/String.h>
+#include <casa/iostream.h>
+#include <casa/iomanip.h>
+#include <casa/Exceptions/Error.h>
+
+#include "MathUtils.h"
+#include "STPol.h"
+#include "STSelector.h"
+
+using namespace asap;
+using namespace casa;
+
+STSelector::STSelector() :
+  taql_("")
+{
+}
+
+STSelector::STSelector( const STSelector&  other ) :
+  intselections_(other.intselections_),
+  stringselections_(other.stringselections_),
+  poltypes_(other.poltypes_),
+  order_(other.order_),
+  taql_(other.taql_)
+{
+}
+
+STSelector& STSelector::operator=( const STSelector& other )
+{
+  if (&other != this) {
+    this->intselections_ = other.intselections_;
+    this->stringselections_ = other.stringselections_;
+    this->taql_ = other.taql_;
+    this->poltypes_ = other.poltypes_;
+    this->order_ = other.order_;
+  }
+  return *this;
+}
+
+STSelector::~STSelector()
+{
+}
+
+void STSelector::setScans( const std::vector< int >& scans )
+{
+  setint("SCANNO", scans);
+}
+
+void STSelector::setBeams( const std::vector< int >& beams )
+{
+  setint("BEAMNO", beams);
+}
+
+void STSelector::setIFs( const std::vector< int >& ifs )
+{
+  setint("IFNO", ifs);
+}
+
+void STSelector::setPolarizations( const std::vector< int >& pols )
+{
+  setint("POLNO", pols);
+  poltypes_ = std::vector<std::string>();
+}
+
+void asap::STSelector::setCycles( const std::vector< int >& cycs )
+{
+  setint("CYCLENO", cycs);
+}
+
+void asap::STSelector::setName( const std::string& sname )
+{
+  std::string sql = "SELECT from $1 WHERE SRCNAME == pattern('"+sname+"')";
+  setTaQL(sql);
+}
+
+void STSelector::setint(const std::string& key, const std::vector< int >& val)
+{
+  if ( val.size() > 0 ) {
+    intselections_[key] = val;
+  }
+}
+
+void STSelector::setstring( const std::string& key,
+                            const std::vector<std::string>& val )
+{
+  if ( val.size() > 0 ) {
+    stringselections_[key] = val;
+  }
+}
+
+void STSelector::setTaQL( const std::string& taql )
+{
+  taql_ = taql;
+}
+
+
+void asap::STSelector::setSortOrder( const std::vector< std::string > & order )
+{
+  order_.resize(order.size(), True);
+  for (unsigned int i=0;i<order.size();++i) {
+    order_[i] = order[i];
+  }
+}
+
+Table STSelector::apply( const Table& tab )
+{
+  if ( empty() ) {
+    return sort(tab);
+  }
+  TableExprNode query;
+  intidmap::const_iterator it;
+  for (it = intselections_.begin(); it != intselections_.end(); ++it) {
+    TableExprNode theset(Vector<Int>( (*it).second ));
+    if ( query.isNull() ) {
+      query = tab.col((*it).first).in(theset);
+    } else {
+      query = tab.col((*it).first).in(theset) && query;
+    }
+  }
+  stringidmap::const_iterator it1;
+  for (it1 = stringselections_.begin(); it1 != stringselections_.end(); ++it1) {
+    TableExprNode theset(mathutil::toVectorString( (*it1).second ));
+    if ( query.isNull() ) {
+      query = tab.col((*it1).first).in(theset);
+    } else {
+      query = tab.col((*it1).first).in(theset) && query;
+    }
+  }
+  // add taql query
+  if ( taql_.size() > 0 ) {
+    Table tmpt = tab;
+
+    if ( !query.isNull() ) { // taql and selection
+      tmpt = tableCommand(taql_, tab(query));
+    } else { // taql only
+      tmpt = tableCommand(taql_, tab);
+    }
+    return sort(tmpt);
+  } else {
+    if ( query.isNull() ) {
+      return sort(tab);
+    } else {
+      return sort(tab(query));
+    }
+  }
+}
+
+std::vector< int > STSelector::getint( const std::string& key ) const
+{
+  if (intselections_.count(key) > 0) {
+    return  intselections_[key];
+  }
+  return  std::vector<int>();
+}
+
+std::vector< int > STSelector::getScans( ) const
+{
+  return getint("SCANNO");
+}
+
+std::vector< int > STSelector::getBeams( ) const
+{
+  return getint("BEAMNO");
+}
+
+std::vector< int > STSelector::getIFs( ) const
+{
+  return getint("IFNO");
+}
+
+std::vector< int > STSelector::getPols( ) const
+{
+  return getint("POLNO");
+}
+
+std::vector< int > asap::STSelector::getCycles( ) const
+{
+  return getint("CYCLENO");
+}
+
+std::string asap::STSelector::print( )
+{
+  ostringstream oss;
+  oss.flags(std::ios_base::left);
+  oss << setw(15) << "Selection:";
+  if ( empty() ) {
+    oss << "none";
+    return String(oss);
+  }
+
+  intidmap::const_iterator it = intselections_.begin();
+  while (it != intselections_.end()) {
+    if ( it != intselections_.begin() )
+      oss << setw(15) << " ";
+    oss << it->first << ": " << Vector<Int>(it->second);
+    ++it;
+    if ( it != intselections_.end() ) oss << endl;
+  }
+  stringidmap::const_iterator it1 = stringselections_.begin();
+  while (it1 != stringselections_.end()) {
+    if ( it1 != stringselections_.begin() )
+      oss << setw(15) << " ";
+    oss << it1->first << ": " << mathutil::toVectorString(it1->second);
+    ++it1;
+    if ( it1 != stringselections_.end() ) oss << endl;
+  }
+  if ( taql_.size() > 0 ) {
+    oss << endl << setw(15) << "" << taql_;
+  }
+  return String(oss);
+}
+
+bool asap::STSelector::empty( ) const
+{
+  return (intselections_.empty() && taql_.size() == 0 );
+}
+
+casa::Table asap::STSelector::sort( const casa::Table & tab )
+{
+  if (order_.nelements() > 0) {
+    Table t = tab.sort(order_);
+    return t;
+  } else {
+    return tab;
+  }
+}
+
+
+void asap::STSelector::setPolFromStrings( const std::vector< std::string >& pols )
+{
+  poltypes_.clear();
+  std::vector<int> polints;
+  std::vector<std::string>::const_iterator strit;
+  for (strit = pols.begin(); strit != pols.end(); ++strit) {
+    std::pair<int, std::string> val;
+    try {
+       val = STPol::polFromString(*strit);
+    } catch (AipsError& e) {
+      poltypes_.clear();
+      throw(e);
+    }
+    polints.push_back(val.first);
+    poltypes_.push_back(val.second);
+  }
+  setint("POLNO", polints);
+}
+
+std::vector< std::string > asap::STSelector::getPolTypes( ) const
+{
+  return poltypes_;
+}
+
+std::vector<std::string> asap::STSelector::getSortOrder() const
+{
+  std::vector<std::string> out;
+  for (uInt i=0;i<order_.nelements(); ++i)
+    out.push_back(order_[i]);
+  return out;
+}
Index: tags/Release2.1.0b/src/STSelector.h
===================================================================
--- tags/Release2.1.0b/src/STSelector.h	(revision 1196)
+++ tags/Release2.1.0b/src/STSelector.h	(revision 1196)
@@ -0,0 +1,92 @@
+//
+// C++ Interface: STSelector
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPSTSELECTOR_H
+#define ASAPSTSELECTOR_H
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include <casa/Containers/Block.h>
+#include <casa/BasicSL/String.h>
+#include <tables/Tables/Table.h>
+
+namespace asap {
+
+/**
+A class to set a subselection of a Scantable
+
+@author Malte Marquarding
+*/
+class STSelector {
+
+public:
+  STSelector();
+  STSelector(const STSelector& other);
+
+  STSelector& operator=(const STSelector& other);
+
+  virtual ~STSelector();
+
+  void setScans(const std::vector<int>& scans);
+  void setBeams(const std::vector<int>& beams);
+  void setIFs(const std::vector<int>& ifs);
+  void setPolarizations(const std::vector<int>& pols);
+  void setPolFromStrings(const std::vector<std::string>& pols);
+  void setCycles(const std::vector<int>& cycs);
+  void setName(const std::string&);
+  virtual void setTaQL(const std::string& taql);
+
+  void setSortOrder(const std::vector<std::string>& order);
+
+  std::vector<int> getScans() const;
+  std::vector<int> getBeams() const;
+  std::vector<int> getIFs() const;
+  std::vector<int> getPols() const;
+  std::vector<int> getCycles() const;
+  std::vector<std::string> getPolTypes() const;
+  std::string getTaQL() const { return taql_; }
+  std::vector<std::string> getSortOrder() const;
+
+  casa::Table apply(const casa::Table& tab);
+  casa::Table operator()(const casa::Table& tab) { return apply(tab); };
+
+  void reset() { intselections_.clear();stringselections_.clear(); taql_ = "";};
+
+  bool empty() const;
+
+  std::string print();
+
+protected:
+  std::vector< int > getint( const std::string& key) const;
+  //std::vector< std::string > getstring( const std::string& key) const;
+
+  void setint(const std::string& key, const std::vector< int >& val);
+  void setstring(const std::string& key, const std::vector< std::string >& val);
+
+private:
+
+  casa::Table sort(const casa::Table& tab);
+
+  typedef std::map<std::string, std::vector<int> > intidmap;
+  typedef std::map<std::string, std::vector<std::string> > stringidmap;
+  // has to be mutable, as to stl limitations
+  mutable intidmap intselections_;
+  mutable stringidmap stringselections_;
+  std::vector<std::string> poltypes_;
+  casa::Block<casa::String> order_;
+  std::string taql_;
+};
+
+}
+
+#endif
Index: tags/Release2.1.0b/src/STSubTable.cpp
===================================================================
--- tags/Release2.1.0b/src/STSubTable.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STSubTable.cpp	(revision 1196)
@@ -0,0 +1,57 @@
+//
+// C++ Implementation: STSubTable
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#include <casa/Exceptions/Error.h>
+#include <tables/Tables/TableDesc.h>
+#include <tables/Tables/SetupNewTab.h>
+#include <tables/Tables/ScaColDesc.h>
+#include <tables/Tables/TableRecord.h>
+
+#include "Scantable.h"
+#include "STSubTable.h"
+
+
+using namespace casa;
+
+namespace asap {
+
+STSubTable::STSubTable( const Scantable& parent, const casa::String& name )
+{
+  TableDesc td("", "1", TableDesc::Scratch);
+  td.addColumn(ScalarColumnDesc<uInt>("ID"));
+  String tabname = parent.table().tableName()+"/"+name;
+  SetupNewTable aNewTab(tabname, td, Table::Scratch);
+  table_ = Table(aNewTab, parent.table().tableType());
+  idCol_.attach(table_,"ID");
+
+}
+STSubTable::STSubTable(Table tab, const String& name)
+{
+  table_ = tab.rwKeywordSet().asTable(name);
+  idCol_.attach(table_,"ID");
+}
+
+
+STSubTable::~STSubTable()
+{
+}
+
+STSubTable& asap::STSubTable::operator=( const STSubTable& other)
+{
+  if (&other != this) {
+    this->table_ = other.table_;
+    idCol_.attach(this->table_,"ID");
+  }
+  return *this;
+}
+
+
+}
Index: tags/Release2.1.0b/src/STSubTable.h
===================================================================
--- tags/Release2.1.0b/src/STSubTable.h	(revision 1196)
+++ tags/Release2.1.0b/src/STSubTable.h	(revision 1196)
@@ -0,0 +1,70 @@
+//
+// C++ Interface: STSubTable
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPSTSUBTABLE_H
+#define ASAPSTSUBTABLE_H
+
+#include <tables/Tables/Table.h>
+#include <tables/Tables/ScalarColumn.h>
+
+#include "Logger.h"
+
+namespace asap {
+
+class Scantable;
+/**
+Abstract base class for all subtables in the Scantable class.
+
+@author Malte Marquarding
+@date $Date:$
+@version $Revision:$
+*/
+class STSubTable : public Logger {
+public:
+  STSubTable() {;}
+  STSubTable( casa::Table tab, const casa::String& name);
+  STSubTable( const Scantable& parent, const casa::String& name );
+
+  virtual ~STSubTable();
+
+  STSubTable& operator=(const STSubTable& other);
+
+
+
+  /**
+   * Add extra columns. To be implemented in derived class
+   */
+  virtual void setup() = 0;
+  // -> virtual bool conformant(const STSubTable& other) = 0;
+
+  virtual const casa::String& name() const = 0;
+
+  /**
+   * Recalculate IDs to be 0-based and incremented by 1 i.e.
+   * rowno == ID
+   * @return the 'old' IDs
+   */
+  casa::Vector<casa::uInt> repopulate();
+
+  const casa::Table& table() const { return table_; }
+  casa::Table table() { return table_; }
+
+protected:
+  casa::Table table_;
+  casa::ScalarColumn<casa::uInt> idCol_;
+
+private:
+
+};
+
+}
+
+#endif
Index: tags/Release2.1.0b/src/STTcal.cpp
===================================================================
--- tags/Release2.1.0b/src/STTcal.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STTcal.cpp	(revision 1196)
@@ -0,0 +1,109 @@
+
+//
+// C++ Implementation: STTcal
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#include <casa/Exceptions/Error.h>
+#include <tables/Tables/TableDesc.h>
+#include <tables/Tables/SetupNewTab.h>
+#include <tables/Tables/ScaColDesc.h>
+#include <tables/Tables/ArrColDesc.h>
+#include <tables/Tables/TableRecord.h>
+#include <tables/Tables/TableParse.h>
+#include <tables/Tables/TableRow.h>
+#include <casa/Containers/RecordField.h>
+
+#include "STTcal.h"
+
+
+using namespace casa;
+
+namespace asap {
+
+const casa::String STTcal::name_ = "TCAL";
+
+STTcal::STTcal(const Scantable& parent) :
+  STSubTable( parent, name_ )
+{
+  setup();
+}
+
+STTcal& asap::STTcal::operator =( const STTcal & other )
+{
+  if ( this != &other ) {
+    static_cast<STSubTable&>(*this) = other;
+    timeCol_.attach(table_,"TIME");
+    tcalCol_.attach(table_,"TCAL");
+  }
+  return *this;
+}
+
+asap::STTcal::STTcal( casa::Table tab ) : STSubTable(tab, name_)
+{
+  timeCol_.attach(table_,"TIME");
+  tcalCol_.attach(table_,"TCAL");
+
+}
+
+STTcal::~STTcal()
+{
+}
+
+void asap::STTcal::setup( )
+{
+  // add to base class table
+  table_.addColumn(ScalarColumnDesc<String>("TIME"));
+  table_.addColumn(ArrayColumnDesc<Float>("TCAL"));
+
+  // new cached columns
+  timeCol_.attach(table_,"TIME");
+  tcalCol_.attach(table_,"TCAL");
+}
+
+uInt STTcal::addEntry( const String& time, const Vector<Float>& cal)
+{
+  // test if this already exists
+  Table result = table_( table_.col("TIME") == time );
+  uInt resultid = 0;
+  if ( result.nrow() > 0) {
+    ROScalarColumn<uInt> c(result, "ID");
+    c.get(0, resultid);
+  } else {
+    uInt rno = table_.nrow();
+    table_.addRow();
+    // get last assigned tcal_id and increment
+    if ( rno > 0 ) {
+      idCol_.get(rno-1, resultid);
+      resultid++;
+    }
+    tcalCol_.put(rno, cal);
+    timeCol_.put(rno, time);
+    idCol_.put(rno, resultid);
+  }
+  return resultid;
+}
+
+void STTcal::getEntry( String& time, Vector<Float>& tcal, uInt id )
+{
+  Table t = table_(table_.col("ID") == Int(id) );
+  if (t.nrow() == 0 ) {
+    throw(AipsError("STTcal::getEntry - id out of range"));
+  }
+  ROTableRow row(t);
+  // get first row - there should only be one matching id
+  const TableRecord& rec = row.get(0);
+  time = rec.asString("TIME");
+  tcal.resize();
+  Vector<Float> out;
+  rec.get("TCAL",out);
+  tcal = out;
+}
+
+} //namespace
Index: tags/Release2.1.0b/src/STTcal.h
===================================================================
--- tags/Release2.1.0b/src/STTcal.h	(revision 1196)
+++ tags/Release2.1.0b/src/STTcal.h	(revision 1196)
@@ -0,0 +1,57 @@
+//
+// C++ Interface: STTcal
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPSTTCAL_H
+#define ASAPSTTCAL_H
+
+#include <casa/aips.h>
+#include <casa/BasicSL/String.h>
+#include <tables/Tables/Table.h>
+#include <tables/Tables/ScalarColumn.h>
+#include <tables/Tables/ArrayColumn.h>
+
+#include "STSubTable.h"
+
+namespace asap {
+
+/**
+The Tcal subtable of the Scantable
+
+@author Malte Marquarding
+*/
+class STTcal : public STSubTable {
+public:
+  STTcal() {;}
+  STTcal(casa::Table tab);
+  STTcal( const Scantable& parent);
+
+  virtual ~STTcal();
+
+  STTcal& operator=(const STTcal& other);
+
+  casa::uInt addEntry( const casa::String& time,
+                       const casa::Vector<casa::Float>& tcal);
+  void getEntry( casa::String& time, casa::Vector<casa::Float>& tcal,
+                 casa::uInt id );
+
+  const casa::String& name() const { return name_; }
+
+private:
+  void setup();
+  static const casa::String name_;
+  //casa::Table table_;
+  casa::ArrayColumn<casa::Float> tcalCol_;
+  casa::ScalarColumn<casa::String> timeCol_;
+};
+
+}
+
+#endif
Index: tags/Release2.1.0b/src/STWeather.cpp
===================================================================
--- tags/Release2.1.0b/src/STWeather.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STWeather.cpp	(revision 1196)
@@ -0,0 +1,127 @@
+//
+// C++ Implementation: STWeather
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#include <casa/Exceptions/Error.h>
+#include <tables/Tables/TableDesc.h>
+#include <tables/Tables/SetupNewTab.h>
+#include <tables/Tables/ScaColDesc.h>
+#include <tables/Tables/TableRecord.h>
+#include <tables/Tables/TableParse.h>
+#include <tables/Tables/TableRow.h>
+#include <casa/Containers/RecordField.h>
+
+#include "STWeather.h"
+
+
+using namespace casa;
+
+namespace asap {
+
+const casa::String STWeather::name_ = "WEATHER";
+
+STWeather::STWeather(const Scantable& parent) :
+  STSubTable( parent, name_ )
+{
+  setup();
+}
+
+
+asap::STWeather::STWeather( casa::Table tab ) : STSubTable(tab, name_)
+{
+  temperatureCol_.attach(table_,"TEMPERATURE");
+  pressureCol_.attach(table_,"PRESSURE");
+  humidityCol_.attach(table_,"HUMIDITY");
+  windspeedCol_.attach(table_,"WINDSPEED");
+  windazCol_.attach(table_,"WINDAZ");
+
+}
+
+STWeather::~STWeather()
+{
+}
+
+STWeather & asap::STWeather::operator =( const STWeather & other )
+{
+  if ( this != &other ) {
+    static_cast<STSubTable&>(*this) = other;
+    temperatureCol_.attach(table_,"TEMPERATURE");
+    pressureCol_.attach(table_,"PRESSURE");
+    humidityCol_.attach(table_,"HUMIDITY");
+    windspeedCol_.attach(table_,"WINDSPEED");
+    windazCol_.attach(table_,"WINDAZ");
+  }
+  return *this;
+}
+
+
+void asap::STWeather::setup( )
+{
+  // add to base class table
+  table_.addColumn(ScalarColumnDesc<Float>("TEMPERATURE"));
+  table_.addColumn(ScalarColumnDesc<Float>("PRESSURE"));
+  table_.addColumn(ScalarColumnDesc<Float>("HUMIDITY"));
+  table_.addColumn(ScalarColumnDesc<Float>("WINDSPEED"));
+  table_.addColumn(ScalarColumnDesc<Float>("WINDAZ"));
+
+  // new cached columns
+  temperatureCol_.attach(table_,"TEMPERATURE");
+  pressureCol_.attach(table_,"PRESSURE");
+  humidityCol_.attach(table_,"HUMIDITY");
+  windspeedCol_.attach(table_,"WINDSPEED");
+  windazCol_.attach(table_,"WINDAZ");
+}
+
+uInt STWeather::addEntry( Float temp, Float pressure, Float humidity,
+                          Float wspeed, Float waz )
+{
+  /// @todo this is a zero implementation as none of the telescopes
+  /// fills in this information (yet)
+  uInt nrow = table_.nrow();
+  if ( nrow == 0 ) {
+    table_.addRow();
+    TableRow row(table_);
+    TableRecord& rec = row.record();
+    RecordFieldPtr< Float > rfp;
+    rfp.attachToRecord(rec,"TEMPERATURE");
+    *rfp = temp;
+    rfp.attachToRecord(rec,"PRESSURE");
+    *rfp = pressure;
+    rfp.attachToRecord(rec,"HUMIDITY");
+    *rfp = humidity;
+    rfp.attachToRecord(rec,"WINDSPEED");
+    *rfp = wspeed;
+    rfp.attachToRecord(rec,"WINDAZ");
+    *rfp = waz;
+    row.put(table_.nrow()-1, rec);
+  }
+  return 0;
+}
+
+void STWeather::getEntry( Float& temperature, Float& pressure,
+                          Float& humidity, Float& windspeed, Float& windaz,
+                          uInt id )
+{
+  Table t = table_(table_.col("ID") == Int(id) );
+  if (t.nrow() == 0 ) {
+    throw(AipsError("STWeather::getEntry - id out of range"));
+  }
+  ROTableRow row(t);
+  // get first row - there should only be one matching id
+  const TableRecord& rec = row.get(0);
+  temperature = rec.asFloat("TEMPERATURE");
+  pressure = rec.asDouble("PRESSURE");
+  humidity = rec.asDouble("HUMIDITY");
+  windspeed = rec.asDouble("WINDSPEED");
+  windaz = rec.asDouble("WINDAZ");
+}
+
+}
+
Index: tags/Release2.1.0b/src/STWeather.h
===================================================================
--- tags/Release2.1.0b/src/STWeather.h	(revision 1196)
+++ tags/Release2.1.0b/src/STWeather.h	(revision 1196)
@@ -0,0 +1,62 @@
+//
+// C++ Interface: STWeather
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPSTWEATHER_H
+#define ASAPSTWEATHER_H
+
+#include <casa/aips.h>
+#include <casa/BasicSL/String.h>
+#include <tables/Tables/Table.h>
+#include <tables/Tables/ScalarColumn.h>
+
+#include "STSubTable.h"
+
+namespace asap {
+
+/**
+The Weather subtable of the Scantable
+
+@author Malte Marquarding
+*/
+class STWeather : public STSubTable {
+public:
+  STWeather() {;}
+  STWeather(casa::Table tab);
+  STWeather( const Scantable& parent);
+
+  virtual ~STWeather();
+
+  STWeather& operator=(const STWeather& other);
+
+  casa::uInt addEntry( casa::Float temperature, casa::Float pressure,
+                       casa::Float humidity,
+                       casa::Float windspeed, casa::Float windaz);
+
+  void getEntry( casa::Float& temperature, casa::Float& pressure,
+                       casa::Float& humidity,
+                       casa::Float& windspeed, casa::Float& windaz,
+                       casa::uInt id);
+
+  const casa::String& name() const { return name_; }
+
+private:
+  void setup();
+  static const casa::String name_;
+  //casa::Table table_;
+  //casa::ScalarColumn<casa::uInt> freqidCol_;
+  casa::ScalarColumn<casa::Float> pressureCol_, temperatureCol_,
+                                  humidityCol_,
+                                  windspeedCol_, windazCol_;
+};
+
+}
+
+#endif
Index: tags/Release2.1.0b/src/STWriter.cpp
===================================================================
--- tags/Release2.1.0b/src/STWriter.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/STWriter.cpp	(revision 1196)
@@ -0,0 +1,330 @@
+//#---------------------------------------------------------------------------
+//# STWriter.cc: ASAP class to write out single dish spectra.
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+
+#include <string>
+
+#include <casa/aips.h>
+#include <casa/Arrays/Array.h>
+#include <casa/Arrays/Vector.h>
+#include <casa/BasicSL/Complex.h>
+#include <casa/Utilities/CountedPtr.h>
+#include <casa/Utilities/Assert.h>
+
+#include <atnf/PKSIO/PKSMS2writer.h>
+#include <atnf/PKSIO/PKSSDwriter.h>
+
+#include <tables/Tables/Table.h>
+#include <tables/Tables/TableIter.h>
+#include <tables/Tables/TableRow.h>
+#include <tables/Tables/ArrayColumn.h>
+
+//#include "SDFITSImageWriter.h"
+#include "STAsciiWriter.h"
+#include "STHeader.h"
+
+#include "STWriter.h"
+
+using namespace casa;
+namespace asap {
+
+STWriter::STWriter(const std::string &format)
+{
+  format_ = format;
+  String t(format_);
+  t.upcase();
+  if (t== "MS2") {
+    writer_ = new PKSMS2writer();
+  } else if (t== "SDFITS") {
+    writer_ = new PKSSDwriter();
+  } else if (t== "FITS") {
+    writer_ = 0;
+  } else if (t== "ASCII") {
+    writer_ = 0;
+  } else {
+    throw (AipsError("Unrecognized export format"));
+  }
+}
+
+STWriter::~STWriter()
+{
+   if (writer_) {
+     delete writer_;
+   }
+}
+
+Int STWriter::setFormat(const std::string &format)
+{
+  if (format != format_) {
+    if (writer_) delete writer_;
+  }
+
+  format_ = format;
+  String t(format_);
+  t.upcase();
+  if (t== "MS2") {
+    writer_ = new PKSMS2writer();
+  } else if (t== "SDFITS") {
+    writer_ = new PKSSDwriter();
+  } else if (t== "FITS") {
+    writer_ = 0;
+  } else if (t== "ASCII") {
+    writer_ = 0;
+  } else {
+    throw (AipsError("Unrecognized Format"));
+  }
+  return 0;
+}
+
+Int STWriter::write(const CountedPtr<Scantable> in,
+                    const std::string &filename)
+{
+
+// Image FITS
+
+  if (format_=="FITS") {
+//      Bool verbose = True;
+//      SDFITSImageWriter iw;
+//      if (iw.write(*in, filename, verbose)) {
+//         return 0;
+//      } else {
+//         return 1;
+//      }
+  } else if (format_=="ASCII") {
+     STAsciiWriter iw;
+     if (iw.write(*in, filename)) {
+        return 0;
+     } else {
+        return 1;
+     }
+  }
+
+  // MS or SDFITS
+
+  // Extract the header from the table.
+  STHeader hdr = in->getHeader();
+  //const Int nPol  = hdr.npol;
+  //const Int nChan = hdr.nchan;
+  int nIF = in->nif();
+  Vector<uInt> nPol(nIF),nChan(nIF);
+  Vector<Bool> havexpol(nIF);
+  for (int i=0;i<nIF;++i) {
+    nPol(i) = in->npol();
+    nChan(i) = in->nchan(i);
+    havexpol(i) = nPol(i) > 2;
+  }
+
+  const Table table = in->table();
+//   ROArrayColumn<uInt> freqIDCol(table, "FREQ_ID");
+//   Vector<uInt> freqIDs;
+
+  // Create the output file and write static data.
+  Int status;
+  //Bool havexpol = Bool(in->npol() > 2);
+  status = writer_->create(String(filename), hdr.observer, hdr.project,
+                               hdr.antennaname, hdr.antennaposition,
+                               hdr.obstype, hdr.equinox, hdr.freqref,
+                               nChan, nPol, havexpol, False);
+  if ( status ) {
+    throw(AipsError("Failed to create output file"));
+  }
+
+  Double          srcVel;
+
+  String          fieldName, srcName, tcalTime;
+  Vector<Float>   calFctr, sigma, tcal, tsys;
+  Vector<Double>  direction(2), scanRate(2), srcDir(2), srcPM(2);
+  Matrix<Float>   spectra;
+  Matrix<uChar>   flagtra;
+  Complex         xCalFctr;
+  Int count = 0;
+  Int scanno = 1;
+  // use spearate iterators to ensure renumbering of all numbers
+  TableIterator scanit(table, "SCANNO");
+  while (!scanit.pastEnd() ) {
+    Table stable = scanit.table();
+    TableIterator beamit(stable, "BEAMNO");
+    Int beamno = 1;
+    while (!beamit.pastEnd() ) {
+      Table btable = beamit.table();
+      // position only varies by beam
+      MDirection::ScalarColumn dirCol(btable, "DIRECTION");
+      Vector<Double> direction = dirCol(0).getAngle("rad").getValue();
+      TableIterator cycit(btable, "CYCLENO");
+      ROArrayColumn<Double> srateCol(btable, "SCANRATE");
+      srateCol.get(0, scanRate);
+      ROArrayColumn<Double> spmCol(btable, "SRCPROPERMOTION");
+      spmCol.get(0, srcPM);
+      ROArrayColumn <Double> sdirCol(btable, "SRCDIRECTION");
+      sdirCol.get(0, srcDir);
+      ROScalarColumn<Double> svelCol(btable, "SRCVELOCITY");
+      svelCol.get(0, srcVel);
+      Int cycno = 1;
+      while (!cycit.pastEnd() ) {
+        Table ctable = cycit.table();
+        TableIterator ifit(ctable, "IFNO");
+        Int ifno = 1;
+        while (!ifit.pastEnd() ) {
+          Table itable = ifit.table();
+          TableRow row(itable);
+          // use the first row to fill in all the "metadata"
+          const TableRecord& rec = row.get(0);
+          ROArrayColumn<Float> specCol(itable, "SPECTRA");
+          uInt nchan = specCol(0).nelements();
+          Double cdelt,crval,crpix, restfreq;
+          Float focusAxi, focusTan, focusRot,
+                temperature, pressure, humidity, windSpeed, windAz;
+          Float tmp0,tmp1,tmp2,tmp3,tmp4;
+          Vector<Float> tcalval;
+          String stmp0,stmp1, tcalt;
+          in->frequencies().getEntry(crpix,crval,cdelt, rec.asuInt("FREQ_ID"));
+          in->focus().getEntry(focusAxi, focusTan, focusRot,
+                               tmp0,tmp1,tmp2,tmp3,tmp4,
+                               rec.asuInt("FOCUS_ID"));
+          in->molecules().getEntry(restfreq,stmp0,stmp1,rec.asuInt("MOLECULE_ID"));
+          in->tcal().getEntry(tcalt,tcalval,rec.asuInt("TCAL_ID"));
+          in->weather().getEntry(temperature, pressure, humidity,
+                                 windSpeed, windAz,
+                                 rec.asuInt("WEATHER_ID"));
+          Double pixel = Double(nchan/2);
+          Double refFreqNew = (pixel-crpix)*cdelt + crval;
+          // ok, now we have nrows for the n polarizations in this table
+          Matrix<Float> specs;
+          Matrix<uChar> flags;
+          Vector<Complex> xpol;
+          polConversion(specs, flags, xpol, itable);
+          Vector<Float> tsys = tsysFromTable(itable);
+          // dummy data
+          uInt npol = specs.ncolumn();
+
+          Matrix<Float>   baseLin(npol,2, 0.0f);
+          Matrix<Float>   baseSub(npol,9, 0.0f);
+          Complex         xCalFctr;
+          Vector<Double>  scanRate(2, 0.0);
+          Vector<Float>   sigma(npol, 0.0f);
+          Vector<Float>   calFctr(npol, 0.0f);
+          status = writer_->write(scanno, cycno, rec.asDouble("TIME"),
+                                      rec.asDouble("INTERVAL"),
+                                      rec.asString("FIELDNAME"),
+                                      rec.asString("SRCNAME"),
+                                      srcDir, srcPM, srcVel,hdr.obstype,
+                                      ifno,
+                                      refFreqNew, nchan*abs(cdelt), cdelt,
+                                      restfreq,
+                                      tcal,
+                                      tcalt,
+                                      rec.asFloat("AZIMUTH"),
+                                      rec.asFloat("ELEVATION"),
+                                      rec.asFloat("PARANGLE"),
+                                      focusAxi, focusTan, focusRot,
+                                      temperature,
+                                      pressure, humidity, windSpeed, windAz,
+                                      rec.asInt("REFBEAMNO")+1, beamno,
+                                      direction,
+                                      scanRate,
+                                      tsys,
+                                      sigma, calFctr,// not in scantable
+                                      baseLin, baseSub,// not in scantable
+                                      specs, flags,
+                                      xCalFctr,//
+                                      xpol);
+          if ( status ) {
+            writer_->close();
+            throw(AipsError("STWriter: Failed to export Scantable."));
+          }
+          ++count;
+          ++ifno;
+          ++ifit;
+        }
+        ++cycno;
+        ++cycit;
+      }
+      ++beamno;
+      ++beamit;
+    }
+    ++scanno;
+    ++scanit;
+  }
+  ostringstream oss;
+  oss << "STWriter: wrote " << count << " rows to " << filename;
+  pushLog(String(oss));
+  writer_->close();
+
+  return 0;
+}
+
+Vector<Float> STWriter::tsysFromTable(const Table& tab)
+{
+  ROArrayColumn<Float> tsysCol(tab, "TSYS");
+  Vector<Float> out(tab.nrow());
+  Vector<Float> tmp;
+  for (uInt i=0; i<tab.nrow(); ++i) {
+    tmp.resize();
+    tmp = tsysCol(i);
+    out[i] = tmp[0];
+  }
+  return out;
+}
+
+void STWriter::polConversion( Matrix< Float >& specs, Matrix< uChar >& flags,
+                              Vector< Complex > & xpol, const Table & tab )
+{
+  String poltype = tab.keywordSet().asString("POLTYPE");
+  if ( poltype != "linear") {
+    String msg = "poltype = " + poltype + " not yet supported in output.";
+    throw(AipsError(msg));
+  }
+  ROArrayColumn<Float> specCol(tab, "SPECTRA");
+  ROArrayColumn<uChar> flagCol(tab, "FLAGTRA");
+  uInt nchan = specCol(0).nelements();
+  uInt ncol = (tab.nrow()==1 ? 1: 2 );
+  specs.resize(nchan, ncol);
+  flags.resize(nchan, ncol);
+  // the linears
+  for (uInt i=0; i<ncol; ++i) {
+    specs.column(i) = specCol(i);
+    flags.column(i) = flagCol(i);
+  }
+  // now the complex if exists
+  Bool hasxpol = False;
+  xpol.resize();
+  if ( tab.nrow() == 4 ) {
+    hasxpol = True;
+    xpol.resize(nchan);
+    Vector<Float> reals, imags;
+    reals = specCol(2); imags = specCol(3);
+    for (uInt k=0; k < nchan; ++k) {
+      xpol[k] = Complex(reals[k], imags[k]);
+    }
+  }
+}
+
+
+}
Index: tags/Release2.1.0b/src/STWriter.h
===================================================================
--- tags/Release2.1.0b/src/STWriter.h	(revision 1196)
+++ tags/Release2.1.0b/src/STWriter.h	(revision 1196)
@@ -0,0 +1,88 @@
+//#---------------------------------------------------------------------------
+//# STWriter.h: ASAP class to write out single dish spectra.
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+#ifndef STWRITER_H
+#define STWRITER_H
+
+#include <string>
+
+#include <casa/aips.h>
+#include <casa/Utilities/CountedPtr.h>
+
+#include "Logger.h"
+#include "Scantable.h"
+
+class PKSwriter;
+class casa::Table;
+
+namespace asap {
+/**
+  * This exports the ASAP internal data format to othe formats,
+  * such as "SDFITS", "FITS", "MS2" or "ASCII"
+  *
+  * @brief Export of ASAP data container into foreign formats
+  * @author Malte Marquarding
+  * @date 2006-03-08
+  * @version 2.0a
+*/
+class STWriter : public Logger {
+public:
+  STWriter(const string &format = "SDFITS");
+  virtual ~STWriter();
+
+  /**
+   * Set the format the data should be exported in
+   * @param format an be "SDFITS", "FITS", "MS2" or "ASCII"
+   * @return ststus code from PKSwriter
+   */
+  casa::Int setFormat(const string& format = "SDFITS");
+
+  /**
+   * Write the data to a file (directory)
+   * @param table the Scantable object
+   * @param filename the output file name
+   * @return
+   */
+  casa::Int write(const casa::CountedPtr<Scantable> table,
+            const string& filename);
+
+private:
+  casa::Vector<casa::Float> tsysFromTable(const casa::Table& tab);
+
+  void polConversion( casa::Matrix<casa::Float>& spec,
+                      casa::Matrix<casa::uChar>& flag,
+                      casa::Vector<casa::Complex>& xpol,
+                      const casa::Table& tab);
+  std::string     format_;
+  PKSwriter* writer_;
+};
+
+}// namespace
+#endif
Index: tags/Release2.1.0b/src/STWriterWrapper.h
===================================================================
--- tags/Release2.1.0b/src/STWriterWrapper.h	(revision 1196)
+++ tags/Release2.1.0b/src/STWriterWrapper.h	(revision 1196)
@@ -0,0 +1,55 @@
+//#---------------------------------------------------------------------------
+//# STWriterWrapper.h: Wrapper classes to use CountedPtr
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+#ifndef STWRITERWRAPPER_H
+#define STWRITERWRAPPER_H
+
+#include <vector>
+#include <string>
+
+#include "ScantableWrapper.h"
+
+#include "STWriter.h"
+
+namespace asap {
+
+class STWriterWrapper : public STWriter {
+public:
+  //STWriterWrapper() {;}
+  virtual ~STWriterWrapper() {;}
+  STWriterWrapper(const string& format = "SDFITS") : STWriter(format) {;}
+
+  casa::Int write(const ScantableWrapper& table, const string &filename) {
+    return STWriter::write(table.getCP(), filename);
+  }
+};
+
+} // namespace asap
+#endif
Index: tags/Release2.1.0b/src/Scantable.cpp
===================================================================
--- tags/Release2.1.0b/src/Scantable.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/Scantable.cpp	(revision 1196)
@@ -0,0 +1,1016 @@
+//
+// C++ Implementation: Scantable
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2005
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#include <map>
+
+#include <casa/aips.h>
+#include <casa/iostream.h>
+#include <casa/iomanip.h>
+#include <casa/OS/Path.h>
+#include <casa/OS/File.h>
+#include <casa/Arrays/Array.h>
+#include <casa/Arrays/ArrayMath.h>
+#include <casa/Arrays/MaskArrMath.h>
+#include <casa/Arrays/ArrayLogical.h>
+#include <casa/Arrays/ArrayAccessor.h>
+#include <casa/Arrays/VectorSTLIterator.h>
+#include <casa/Arrays/Vector.h>
+#include <casa/BasicMath/Math.h>
+#include <casa/BasicSL/Constants.h>
+#include <casa/Quanta/MVAngle.h>
+#include <casa/Containers/RecordField.h>
+#include <casa/Utilities/GenSort.h>
+
+#include <tables/Tables/TableParse.h>
+#include <tables/Tables/TableDesc.h>
+#include <tables/Tables/TableCopy.h>
+#include <tables/Tables/SetupNewTab.h>
+#include <tables/Tables/ScaColDesc.h>
+#include <tables/Tables/ArrColDesc.h>
+#include <tables/Tables/TableRow.h>
+#include <tables/Tables/TableVector.h>
+#include <tables/Tables/TableIter.h>
+
+#include <tables/Tables/ExprNode.h>
+#include <tables/Tables/TableRecord.h>
+#include <measures/Measures/MFrequency.h>
+#include <measures/Measures/MEpoch.h>
+#include <measures/Measures/MeasTable.h>
+#include <measures/Measures/MeasRef.h>
+#include <measures/TableMeasures/TableMeasRefDesc.h>
+#include <measures/TableMeasures/TableMeasValueDesc.h>
+#include <measures/TableMeasures/TableMeasDesc.h>
+#include <measures/TableMeasures/ScalarMeasColumn.h>
+#include <coordinates/Coordinates/CoordinateUtil.h>
+#include <casa/Quanta/MVTime.h>
+#include <casa/Quanta/MVAngle.h>
+
+#include "Scantable.h"
+#include "STPolLinear.h"
+#include "STPolCircular.h"
+#include "STPolStokes.h"
+#include "STAttr.h"
+#include "MathUtils.h"
+
+using namespace casa;
+
+namespace asap {
+
+std::map<std::string, STPol::STPolFactory *> Scantable::factories_;
+
+void Scantable::initFactories() {
+  if ( factories_.empty() ) {
+    Scantable::factories_["linear"] = &STPolLinear::myFactory;
+    Scantable::factories_["circular"] = &STPolStokes::myFactory;
+    Scantable::factories_["stokes"] = &STPolStokes::myFactory;
+  }
+}
+
+Scantable::Scantable(Table::TableType ttype) :
+  type_(ttype)
+{
+  initFactories();
+  setupMainTable();
+  freqTable_ = STFrequencies(*this);
+  table_.rwKeywordSet().defineTable("FREQUENCIES", freqTable_.table());
+  weatherTable_ = STWeather(*this);
+  table_.rwKeywordSet().defineTable("WEATHER", weatherTable_.table());
+  focusTable_ = STFocus(*this);
+  table_.rwKeywordSet().defineTable("FOCUS", focusTable_.table());
+  tcalTable_ = STTcal(*this);
+  table_.rwKeywordSet().defineTable("TCAL", tcalTable_.table());
+  moleculeTable_ = STMolecules(*this);
+  table_.rwKeywordSet().defineTable("MOLECULES", moleculeTable_.table());
+  historyTable_ = STHistory(*this);
+  table_.rwKeywordSet().defineTable("HISTORY", historyTable_.table());
+  fitTable_ = STFit(*this);
+  table_.rwKeywordSet().defineTable("FIT", fitTable_.table());
+  originalTable_ = table_;
+  attach();
+}
+
+Scantable::Scantable(const std::string& name, Table::TableType ttype) :
+  type_(ttype)
+{
+  initFactories();
+  Table tab(name, Table::Update);
+  uInt version = tab.keywordSet().asuInt("VERSION");
+  if (version != version_) {
+    throw(AipsError("Unsupported version of ASAP file."));
+  }
+  if ( type_ == Table::Memory ) {
+    table_ = tab.copyToMemoryTable(generateName());
+  } else {
+    table_ = tab;
+  }
+
+  attachSubtables();
+  originalTable_ = table_;
+  attach();
+}
+
+Scantable::Scantable( const Scantable& other, bool clear )
+{
+  // with or without data
+  String newname = String(generateName());
+  type_ = other.table_.tableType();
+  if ( other.table_.tableType() == Table::Memory ) {
+      if ( clear ) {
+        table_ = TableCopy::makeEmptyMemoryTable(newname,
+                                                 other.table_, True);
+      } else
+        table_ = other.table_.copyToMemoryTable(newname);
+  } else {
+      other.table_.deepCopy(newname, Table::New, False,
+                            other.table_.endianFormat(),
+                            Bool(clear));
+      table_ = Table(newname, Table::Update);
+      table_.markForDelete();
+  }
+  /// @todo reindex SCANNO, recompute nbeam, nif, npol
+  if ( clear ) copySubtables(other);
+  attachSubtables();
+  originalTable_ = table_;
+  attach();
+}
+
+void Scantable::copySubtables(const Scantable& other) {
+  Table t = table_.rwKeywordSet().asTable("FREQUENCIES");
+  TableCopy::copyRows(t, other.freqTable_.table());
+  t = table_.rwKeywordSet().asTable("FOCUS");
+  TableCopy::copyRows(t, other.focusTable_.table());
+  t = table_.rwKeywordSet().asTable("WEATHER");
+  TableCopy::copyRows(t, other.weatherTable_.table());
+  t = table_.rwKeywordSet().asTable("TCAL");
+  TableCopy::copyRows(t, other.tcalTable_.table());
+  t = table_.rwKeywordSet().asTable("MOLECULES");
+  TableCopy::copyRows(t, other.moleculeTable_.table());
+  t = table_.rwKeywordSet().asTable("HISTORY");
+  TableCopy::copyRows(t, other.historyTable_.table());
+  t = table_.rwKeywordSet().asTable("FIT");
+  TableCopy::copyRows(t, other.fitTable_.table());
+}
+
+void Scantable::attachSubtables()
+{
+  freqTable_ = STFrequencies(table_);
+  focusTable_ = STFocus(table_);
+  weatherTable_ = STWeather(table_);
+  tcalTable_ = STTcal(table_);
+  moleculeTable_ = STMolecules(table_);
+  historyTable_ = STHistory(table_);
+  fitTable_ = STFit(table_);
+}
+
+Scantable::~Scantable()
+{
+  //cout << "~Scantable() " << this << endl;
+}
+
+void Scantable::setupMainTable()
+{
+  TableDesc td("", "1", TableDesc::Scratch);
+  td.comment() = "An ASAP Scantable";
+  td.rwKeywordSet().define("VERSION", uInt(version_));
+
+  // n Cycles
+  td.addColumn(ScalarColumnDesc<uInt>("SCANNO"));
+  // new index every nBeam x nIF x nPol
+  td.addColumn(ScalarColumnDesc<uInt>("CYCLENO"));
+
+  td.addColumn(ScalarColumnDesc<uInt>("BEAMNO"));
+  td.addColumn(ScalarColumnDesc<uInt>("IFNO"));
+  // linear, circular, stokes
+  td.rwKeywordSet().define("POLTYPE", String("linear"));
+  td.addColumn(ScalarColumnDesc<uInt>("POLNO"));
+
+  td.addColumn(ScalarColumnDesc<uInt>("FREQ_ID"));
+  td.addColumn(ScalarColumnDesc<uInt>("MOLECULE_ID"));
+  td.addColumn(ScalarColumnDesc<Int>("REFBEAMNO"));
+
+  td.addColumn(ScalarColumnDesc<Double>("TIME"));
+  TableMeasRefDesc measRef(MEpoch::UTC); // UTC as default
+  TableMeasValueDesc measVal(td, "TIME");
+  TableMeasDesc<MEpoch> mepochCol(measVal, measRef);
+  mepochCol.write(td);
+
+  td.addColumn(ScalarColumnDesc<Double>("INTERVAL"));
+
+  td.addColumn(ScalarColumnDesc<String>("SRCNAME"));
+  // Type of source (on=0, off=1, other=-1)
+  td.addColumn(ScalarColumnDesc<Int>("SRCTYPE", Int(-1)));
+  td.addColumn(ScalarColumnDesc<String>("FIELDNAME"));
+
+  //The actual Data Vectors
+  td.addColumn(ArrayColumnDesc<Float>("SPECTRA"));
+  td.addColumn(ArrayColumnDesc<uChar>("FLAGTRA"));
+  td.addColumn(ArrayColumnDesc<Float>("TSYS"));
+
+  td.addColumn(ArrayColumnDesc<Double>("DIRECTION",
+                                       IPosition(1,2),
+                                       ColumnDesc::Direct));
+  TableMeasRefDesc mdirRef(MDirection::J2000); // default
+  TableMeasValueDesc tmvdMDir(td, "DIRECTION");
+  // the TableMeasDesc gives the column a type
+  TableMeasDesc<MDirection> mdirCol(tmvdMDir, mdirRef);
+  // a uder set table type e.g. GALCTIC, B1950 ...
+  td.rwKeywordSet().define("DIRECTIONREF", String("J2000"));
+  // writing create the measure column
+  mdirCol.write(td);
+  td.addColumn(ScalarColumnDesc<Float>("AZIMUTH"));
+  td.addColumn(ScalarColumnDesc<Float>("ELEVATION"));
+  td.addColumn(ScalarColumnDesc<Float>("PARANGLE"));
+  td.addColumn(ScalarColumnDesc<Float>("OPACITY"));
+
+  td.addColumn(ScalarColumnDesc<uInt>("TCAL_ID"));
+  ScalarColumnDesc<Int> fitColumn("FIT_ID");
+  fitColumn.setDefault(Int(-1));
+  td.addColumn(fitColumn);
+
+  td.addColumn(ScalarColumnDesc<uInt>("FOCUS_ID"));
+  td.addColumn(ScalarColumnDesc<uInt>("WEATHER_ID"));
+
+  // columns which just get dragged along, as they aren't used in asap
+  td.addColumn(ScalarColumnDesc<Double>("SRCVELOCITY"));
+  td.addColumn(ArrayColumnDesc<Double>("SRCPROPERMOTION"));
+  td.addColumn(ArrayColumnDesc<Double>("SRCDIRECTION"));
+  td.addColumn(ArrayColumnDesc<Double>("SCANRATE"));
+
+  td.rwKeywordSet().define("OBSMODE", String(""));
+
+  // Now create Table SetUp from the description.
+  SetupNewTable aNewTab(generateName(), td, Table::Scratch);
+  table_ = Table(aNewTab, type_, 0);
+  originalTable_ = table_;
+}
+
+
+void Scantable::attach()
+{
+  timeCol_.attach(table_, "TIME");
+  srcnCol_.attach(table_, "SRCNAME");
+  srctCol_.attach(table_, "SRCTYPE");
+  specCol_.attach(table_, "SPECTRA");
+  flagsCol_.attach(table_, "FLAGTRA");
+  tsysCol_.attach(table_, "TSYS");
+  cycleCol_.attach(table_,"CYCLENO");
+  scanCol_.attach(table_, "SCANNO");
+  beamCol_.attach(table_, "BEAMNO");
+  ifCol_.attach(table_, "IFNO");
+  polCol_.attach(table_, "POLNO");
+  integrCol_.attach(table_, "INTERVAL");
+  azCol_.attach(table_, "AZIMUTH");
+  elCol_.attach(table_, "ELEVATION");
+  dirCol_.attach(table_, "DIRECTION");
+  paraCol_.attach(table_, "PARANGLE");
+  fldnCol_.attach(table_, "FIELDNAME");
+  rbeamCol_.attach(table_, "REFBEAMNO");
+
+  mfitidCol_.attach(table_,"FIT_ID");
+  mfreqidCol_.attach(table_, "FREQ_ID");
+  mtcalidCol_.attach(table_, "TCAL_ID");
+  mfocusidCol_.attach(table_, "FOCUS_ID");
+  mmolidCol_.attach(table_, "MOLECULE_ID");
+}
+
+void Scantable::setHeader(const STHeader& sdh)
+{
+  table_.rwKeywordSet().define("nIF", sdh.nif);
+  table_.rwKeywordSet().define("nBeam", sdh.nbeam);
+  table_.rwKeywordSet().define("nPol", sdh.npol);
+  table_.rwKeywordSet().define("nChan", sdh.nchan);
+  table_.rwKeywordSet().define("Observer", sdh.observer);
+  table_.rwKeywordSet().define("Project", sdh.project);
+  table_.rwKeywordSet().define("Obstype", sdh.obstype);
+  table_.rwKeywordSet().define("AntennaName", sdh.antennaname);
+  table_.rwKeywordSet().define("AntennaPosition", sdh.antennaposition);
+  table_.rwKeywordSet().define("Equinox", sdh.equinox);
+  table_.rwKeywordSet().define("FreqRefFrame", sdh.freqref);
+  table_.rwKeywordSet().define("FreqRefVal", sdh.reffreq);
+  table_.rwKeywordSet().define("Bandwidth", sdh.bandwidth);
+  table_.rwKeywordSet().define("UTC", sdh.utc);
+  table_.rwKeywordSet().define("FluxUnit", sdh.fluxunit);
+  table_.rwKeywordSet().define("Epoch", sdh.epoch);
+  table_.rwKeywordSet().define("POLTYPE", sdh.poltype);
+}
+
+STHeader Scantable::getHeader() const
+{
+  STHeader sdh;
+  table_.keywordSet().get("nBeam",sdh.nbeam);
+  table_.keywordSet().get("nIF",sdh.nif);
+  table_.keywordSet().get("nPol",sdh.npol);
+  table_.keywordSet().get("nChan",sdh.nchan);
+  table_.keywordSet().get("Observer", sdh.observer);
+  table_.keywordSet().get("Project", sdh.project);
+  table_.keywordSet().get("Obstype", sdh.obstype);
+  table_.keywordSet().get("AntennaName", sdh.antennaname);
+  table_.keywordSet().get("AntennaPosition", sdh.antennaposition);
+  table_.keywordSet().get("Equinox", sdh.equinox);
+  table_.keywordSet().get("FreqRefFrame", sdh.freqref);
+  table_.keywordSet().get("FreqRefVal", sdh.reffreq);
+  table_.keywordSet().get("Bandwidth", sdh.bandwidth);
+  table_.keywordSet().get("UTC", sdh.utc);
+  table_.keywordSet().get("FluxUnit", sdh.fluxunit);
+  table_.keywordSet().get("Epoch", sdh.epoch);
+  table_.keywordSet().get("POLTYPE", sdh.poltype);
+  return sdh;
+}
+
+void asap::Scantable::setSourceType( int stype )
+{
+  if ( stype < 0 || stype > 1 )
+    throw(AipsError("Illegal sourcetype."));
+  TableVector<Int> tabvec(table_, "SRCTYPE");
+  tabvec = Int(stype);
+}
+
+bool Scantable::conformant( const Scantable& other )
+{
+  return this->getHeader().conformant(other.getHeader());
+}
+
+
+
+std::string Scantable::formatSec(Double x) const
+{
+  Double xcop = x;
+  MVTime mvt(xcop/24./3600.);  // make days
+
+  if (x < 59.95)
+    return  String("      ") + mvt.string(MVTime::TIME_CLEAN_NO_HM, 7)+"s";
+  else if (x < 3599.95)
+    return String("   ") + mvt.string(MVTime::TIME_CLEAN_NO_H,7)+" ";
+  else {
+    ostringstream oss;
+    oss << setw(2) << std::right << setprecision(1) << mvt.hour();
+    oss << ":" << mvt.string(MVTime::TIME_CLEAN_NO_H,7) << " ";
+    return String(oss);
+  }
+};
+
+std::string Scantable::formatDirection(const MDirection& md) const
+{
+  Vector<Double> t = md.getAngle(Unit(String("rad"))).getValue();
+  Int prec = 7;
+
+  MVAngle mvLon(t[0]);
+  String sLon = mvLon.string(MVAngle::TIME,prec);
+  uInt tp = md.getRef().getType();
+  if (tp == MDirection::GALACTIC ||
+      tp == MDirection::SUPERGAL ) {
+    sLon = mvLon(0.0).string(MVAngle::ANGLE_CLEAN,prec);
+  }
+  MVAngle mvLat(t[1]);
+  String sLat = mvLat.string(MVAngle::ANGLE+MVAngle::DIG2,prec);
+  return sLon + String(" ") + sLat;
+}
+
+
+std::string Scantable::getFluxUnit() const
+{
+  return table_.keywordSet().asString("FluxUnit");
+}
+
+void Scantable::setFluxUnit(const std::string& unit)
+{
+  String tmp(unit);
+  Unit tU(tmp);
+  if (tU==Unit("K") || tU==Unit("Jy")) {
+     table_.rwKeywordSet().define(String("FluxUnit"), tmp);
+  } else {
+     throw AipsError("Illegal unit - must be compatible with Jy or K");
+  }
+}
+
+void Scantable::setInstrument(const std::string& name)
+{
+  bool throwIt = true;
+  // create an Instrument to see if this is valid
+  STAttr::convertInstrument(name, throwIt);
+  String nameU(name);
+  nameU.upcase();
+  table_.rwKeywordSet().define(String("AntennaName"), nameU);
+}
+
+void Scantable::setFeedType(const std::string& feedtype)
+{
+  if ( Scantable::factories_.find(feedtype) ==  Scantable::factories_.end() ) {
+    std::string msg = "Illegal feed type "+ feedtype;
+    throw(casa::AipsError(msg));
+  }
+  table_.rwKeywordSet().define(String("POLTYPE"), feedtype);
+}
+
+MPosition Scantable::getAntennaPosition () const
+{
+  Vector<Double> antpos;
+  table_.keywordSet().get("AntennaPosition", antpos);
+  MVPosition mvpos(antpos(0),antpos(1),antpos(2));
+  return MPosition(mvpos);
+}
+
+void Scantable::makePersistent(const std::string& filename)
+{
+  String inname(filename);
+  Path path(inname);
+  /// @todo reindex SCANNO, recompute nbeam, nif, npol
+  inname = path.expandedName();
+  table_.deepCopy(inname, Table::New);
+}
+
+int Scantable::nbeam( int scanno ) const
+{
+  if ( scanno < 0 ) {
+    Int n;
+    table_.keywordSet().get("nBeam",n);
+    return int(n);
+  } else {
+    // take the first POLNO,IFNO,CYCLENO as nbeam shouldn't vary with these
+    Table t = table_(table_.col("SCANNO") == scanno);
+    ROTableRow row(t);
+    const TableRecord& rec = row.get(0);
+    Table subt = t( t.col("IFNO") == Int(rec.asuInt("IFNO"))
+                    && t.col("POLNO") == Int(rec.asuInt("POLNO"))
+                    && t.col("CYCLENO") == Int(rec.asuInt("CYCLENO")) );
+    ROTableVector<uInt> v(subt, "BEAMNO");
+    return int(v.nelements());
+  }
+  return 0;
+}
+
+int Scantable::nif( int scanno ) const
+{
+  if ( scanno < 0 ) {
+    Int n;
+    table_.keywordSet().get("nIF",n);
+    return int(n);
+  } else {
+    // take the first POLNO,BEAMNO,CYCLENO as nbeam shouldn't vary with these
+    Table t = table_(table_.col("SCANNO") == scanno);
+    ROTableRow row(t);
+    const TableRecord& rec = row.get(0);
+    Table subt = t( t.col("BEAMNO") == Int(rec.asuInt("BEAMNO"))
+                    && t.col("POLNO") == Int(rec.asuInt("POLNO"))
+                    && t.col("CYCLENO") == Int(rec.asuInt("CYCLENO")) );
+    if ( subt.nrow() == 0 ) return 0;
+    ROTableVector<uInt> v(subt, "IFNO");
+    return int(v.nelements());
+  }
+  return 0;
+}
+
+int Scantable::npol( int scanno ) const
+{
+  if ( scanno < 0 ) {
+    Int n;
+    table_.keywordSet().get("nPol",n);
+    return n;
+  } else {
+    // take the first POLNO,IFNO,CYCLENO as nbeam shouldn't vary with these
+    Table t = table_(table_.col("SCANNO") == scanno);
+    ROTableRow row(t);
+    const TableRecord& rec = row.get(0);
+    Table subt = t( t.col("BEAMNO") == Int(rec.asuInt("BEAMNO"))
+                    && t.col("IFNO") == Int(rec.asuInt("IFNO"))
+                    && t.col("CYCLENO") == Int(rec.asuInt("CYCLENO")) );
+    if ( subt.nrow() == 0 ) return 0;
+    ROTableVector<uInt> v(subt, "POLNO");
+    return int(v.nelements());
+  }
+  return 0;
+}
+
+int Scantable::ncycle( int scanno ) const
+{
+  if ( scanno < 0 ) {
+    Block<String> cols(2);
+    cols[0] = "SCANNO";
+    cols[1] = "CYCLENO";
+    TableIterator it(table_, cols);
+    int n = 0;
+    while ( !it.pastEnd() ) {
+      ++n;
+      ++it;
+    }
+    return n;
+  } else {
+    Table t = table_(table_.col("SCANNO") == scanno);
+    ROTableRow row(t);
+    const TableRecord& rec = row.get(0);
+    Table subt = t( t.col("BEAMNO") == Int(rec.asuInt("BEAMNO"))
+                    && t.col("POLNO") == Int(rec.asuInt("POLNO"))
+                    && t.col("IFNO") == Int(rec.asuInt("IFNO")) );
+    if ( subt.nrow() == 0 ) return 0;
+    return int(subt.nrow());
+  }
+  return 0;
+}
+
+
+int Scantable::nrow( int scanno ) const
+{
+  return int(table_.nrow());
+}
+
+int Scantable::nchan( int ifno ) const
+{
+  if ( ifno < 0 ) {
+    Int n;
+    table_.keywordSet().get("nChan",n);
+    return int(n);
+  } else {
+    // take the first SCANNO,POLNO,BEAMNO,CYCLENO as nbeam shouldn't vary with these
+    Table t = table_(table_.col("IFNO") == ifno);
+    if ( t.nrow() == 0 ) return 0;
+    ROArrayColumn<Float> v(t, "SPECTRA");
+    return v.shape(0)(0);
+  }
+  return 0;
+}
+
+int Scantable::nscan() const {
+  Vector<uInt> scannos(scanCol_.getColumn());
+  uInt nout = genSort( scannos, Sort::Ascending,
+                       Sort::QuickSort|Sort::NoDuplicates );
+  return int(nout);
+}
+
+int Scantable::getChannels(int whichrow) const
+{
+  return specCol_.shape(whichrow)(0);
+}
+
+int Scantable::getBeam(int whichrow) const
+{
+  return beamCol_(whichrow);
+}
+
+std::vector<uint> Scantable::getNumbers(ScalarColumn<uInt>& col)
+{
+  Vector<uInt> nos(col.getColumn());
+  uInt n = genSort( nos, Sort::Ascending, Sort::QuickSort|Sort::NoDuplicates );
+  nos.resize(n, True);
+  std::vector<uint> stlout;
+  nos.tovector(stlout);
+  return stlout;
+}
+
+int Scantable::getIF(int whichrow) const
+{
+  return ifCol_(whichrow);
+}
+
+int Scantable::getPol(int whichrow) const
+{
+  return polCol_(whichrow);
+}
+
+std::string Scantable::formatTime(const MEpoch& me, bool showdate) const
+{
+  MVTime mvt(me.getValue());
+  if (showdate)
+    mvt.setFormat(MVTime::YMD);
+  else
+    mvt.setFormat(MVTime::TIME);
+  ostringstream oss;
+  oss << mvt;
+  return String(oss);
+}
+
+void Scantable::calculateAZEL()
+{
+  MPosition mp = getAntennaPosition();
+  MEpoch::ROScalarColumn timeCol(table_, "TIME");
+  ostringstream oss;
+  oss << "Computed azimuth/elevation using " << endl
+      << mp << endl;
+  for (Int i=0; i<nrow(); ++i) {
+    MEpoch me = timeCol(i);
+    MDirection md = getDirection(i);
+    oss  << " Time: " << formatTime(me,False) << " Direction: " << formatDirection(md)
+         << endl << "     => ";
+    MeasFrame frame(mp, me);
+    Vector<Double> azel =
+        MDirection::Convert(md, MDirection::Ref(MDirection::AZEL,
+                                                frame)
+                            )().getAngle("rad").getValue();
+    azCol_.put(i,Float(azel[0]));
+    elCol_.put(i,Float(azel[1]));
+    oss << "azel: " << azel[0]/C::pi*180.0 << " "
+        << azel[1]/C::pi*180.0 << " (deg)" << endl;
+  }
+  pushLog(String(oss));
+}
+
+void Scantable::flag(const std::vector<bool>& msk)
+{
+  if ( selector_.empty()  && msk.size() == 0 )
+    throw(AipsError("Trying to flag whole scantable."));
+  if ( msk.size() == 0 ) {
+    uChar userflag = 1 << 7;
+    for ( uInt i=0; i<table_.nrow(); ++i) {
+      Vector<uChar> flgs = flagsCol_(i);
+      flgs = userflag;
+      flagsCol_.put(i, flgs);
+    }
+    return;
+  }
+  if ( int(msk.size()) != nchan() ) {
+    throw(AipsError("Mask has incorrect number of channels."));
+  }
+  for ( uInt i=0; i<table_.nrow(); ++i) {
+    Vector<uChar> flgs = flagsCol_(i);
+    if ( flgs.nelements() != msk.size() ) {
+      throw(AipsError("Mask has incorrect number of channels."
+                      " Probably varying with IF. Please flag per IF"));
+    }
+    std::vector<bool>::const_iterator it;
+    uInt j = 0;
+    uChar userflag = 1 << 7;
+    for (it = msk.begin(); it != msk.end(); ++it) {
+      if ( *it ) {
+        flgs(j) = userflag;
+      }
+      ++j;
+    }
+    flagsCol_.put(i, flgs);
+  }
+}
+
+std::vector<bool> Scantable::getMask(int whichrow) const
+{
+  Vector<uChar> flags;
+  flagsCol_.get(uInt(whichrow), flags);
+  Vector<Bool> bflag(flags.shape());
+  convertArray(bflag, flags);
+  bflag = !bflag;
+  std::vector<bool> mask;
+  bflag.tovector(mask);
+  return mask;
+}
+
+std::vector<float> Scantable::getSpectrum( int whichrow,
+                                           const std::string& poltype ) const
+{
+  String ptype = poltype;
+  if (poltype == "" ) ptype = getPolType();
+  if ( whichrow  < 0 || whichrow >= nrow() )
+    throw(AipsError("Illegal row number."));
+  std::vector<float> out;
+  Vector<Float> arr;
+  uInt requestedpol = polCol_(whichrow);
+  String basetype = getPolType();
+  if ( ptype == basetype ) {
+    specCol_.get(whichrow, arr);
+  } else {
+    STPol* stpol = 0;
+    stpol =STPol::getPolClass(Scantable::factories_, basetype);
+    try {
+      uInt row = uInt(whichrow);
+      stpol->setSpectra(getPolMatrix(row));
+      Float fang,fhand,parang;
+      fang = focusTable_.getTotalFeedAngle(mfocusidCol_(row));
+      fhand = focusTable_.getFeedHand(mfocusidCol_(row));
+      parang = paraCol_(row);
+      /// @todo re-enable this
+      // disable total feed angle to support paralactifying Caswell style
+      stpol->setPhaseCorrections(parang, -parang, fhand);
+      arr = stpol->getSpectrum(requestedpol, ptype);
+      delete stpol;
+    } catch (AipsError& e) {
+      delete stpol;
+      throw(e);
+    }
+  }
+  if ( arr.nelements() == 0 )
+    pushLog("Not enough polarisations present to do the conversion.");
+  arr.tovector(out);
+  return out;
+}
+
+void asap::Scantable::setSpectrum( const std::vector<float>& spec,
+                                   int whichrow )
+{
+  Vector<Float> spectrum(spec);
+  Vector<Float> arr;
+  specCol_.get(whichrow, arr);
+  if ( spectrum.nelements() != arr.nelements() )
+    throw AipsError("The spectrum has incorrect number of channels.");
+  specCol_.put(whichrow, spectrum);
+}
+
+
+String Scantable::generateName()
+{
+  return (File::newUniqueName("./","temp")).baseName();
+}
+
+const casa::Table& Scantable::table( ) const
+{
+  return table_;
+}
+
+casa::Table& Scantable::table( )
+{
+  return table_;
+}
+
+std::string Scantable::getPolType() const
+{
+  return table_.keywordSet().asString("POLTYPE");
+}
+
+void Scantable::unsetSelection()
+{
+  table_ = originalTable_;
+  attach();
+  selector_.reset();
+}
+
+void Scantable::setSelection( const STSelector& selection )
+{
+  Table tab = const_cast<STSelector&>(selection).apply(originalTable_);
+  if ( tab.nrow() == 0 ) {
+    throw(AipsError("Selection contains no data. Not applying it."));
+  }
+  table_ = tab;
+  attach();
+  selector_ = selection;
+}
+
+std::string Scantable::summary( bool verbose )
+{
+  // Format header info
+  ostringstream oss;
+  oss << endl;
+  oss << asap::SEPERATOR << endl;
+  oss << " Scan Table Summary" << endl;
+  oss << asap::SEPERATOR << endl;
+  oss.flags(std::ios_base::left);
+  oss << setw(15) << "Beams:" << setw(4) << nbeam() << endl
+      << setw(15) << "IFs:" << setw(4) << nif() << endl
+      << setw(15) << "Polarisations:" << setw(4) << npol()
+      << "(" << getPolType() << ")" << endl
+      << setw(15) << "Channels:"  << setw(4) << nchan() << endl;
+  oss << endl;
+  String tmp;
+  oss << setw(15) << "Observer:"
+      << table_.keywordSet().asString("Observer") << endl;
+  oss << setw(15) << "Obs Date:" << getTime(-1,true) << endl;
+  table_.keywordSet().get("Project", tmp);
+  oss << setw(15) << "Project:" << tmp << endl;
+  table_.keywordSet().get("Obstype", tmp);
+  oss << setw(15) << "Obs. Type:" << tmp << endl;
+  table_.keywordSet().get("AntennaName", tmp);
+  oss << setw(15) << "Antenna Name:" << tmp << endl;
+  table_.keywordSet().get("FluxUnit", tmp);
+  oss << setw(15) << "Flux Unit:" << tmp << endl;
+  Vector<Double> vec(moleculeTable_.getRestFrequencies());
+  oss << setw(15) << "Rest Freqs:";
+  if (vec.nelements() > 0) {
+      oss << setprecision(10) << vec << " [Hz]" << endl;
+  } else {
+      oss << "none" << endl;
+  }
+
+  oss << setw(15) << "Abcissa:" << getAbcissaLabel(0) << endl;
+  oss << selector_.print() << endl;
+  oss << endl;
+  // main table
+  String dirtype = "Position ("
+                  + getDirectionRefString()
+                  + ")";
+  oss << setw(5) << "Scan" << setw(15) << "Source"
+      << setw(10) << "Time" << setw(18) << "Integration" << endl;
+  oss << setw(5) << "" << setw(5) << "Beam" << setw(3) << "" << dirtype << endl;
+  oss << setw(10) << "" << setw(3) << "IF" << setw(6) << ""
+      << setw(8) << "Frame" << setw(16)
+      << "RefVal" << setw(10) << "RefPix" << setw(12) << "Increment" <<endl;
+  oss << asap::SEPERATOR << endl;
+  TableIterator iter(table_, "SCANNO");
+  while (!iter.pastEnd()) {
+    Table subt = iter.table();
+    ROTableRow row(subt);
+    MEpoch::ROScalarColumn timeCol(subt,"TIME");
+    const TableRecord& rec = row.get(0);
+    oss << setw(4) << std::right << rec.asuInt("SCANNO")
+        << std::left << setw(1) << ""
+        << setw(15) << rec.asString("SRCNAME")
+        << setw(10) << formatTime(timeCol(0), false);
+    // count the cycles in the scan
+    TableIterator cyciter(subt, "CYCLENO");
+    int nint = 0;
+    while (!cyciter.pastEnd()) {
+      ++nint;
+      ++cyciter;
+    }
+    oss << setw(3) << std::right << nint  << setw(3) << " x " << std::left
+        << setw(6) <<  formatSec(rec.asFloat("INTERVAL")) << endl;
+
+    TableIterator biter(subt, "BEAMNO");
+    while (!biter.pastEnd()) {
+      Table bsubt = biter.table();
+      ROTableRow brow(bsubt);
+      const TableRecord& brec = brow.get(0);
+      uInt row0 = bsubt.rowNumbers(table_)[0];
+      oss << setw(5) << "" <<  setw(4) << std::right << brec.asuInt("BEAMNO")<< std::left;
+      oss  << setw(4) << ""  << formatDirection(getDirection(row0)) << endl;
+      TableIterator iiter(bsubt, "IFNO");
+      while (!iiter.pastEnd()) {
+        Table isubt = iiter.table();
+        ROTableRow irow(isubt);
+        const TableRecord& irec = irow.get(0);
+        oss << setw(10) << "";
+        oss << setw(3) << std::right << irec.asuInt("IFNO") << std::left
+            << setw(2) << "" << frequencies().print(irec.asuInt("FREQ_ID"));
+
+        ++iiter;
+      }
+      ++biter;
+    }
+    ++iter;
+  }
+  /// @todo implement verbose mode
+  return String(oss);
+}
+
+std::string Scantable::getTime(int whichrow, bool showdate) const
+{
+  MEpoch::ROScalarColumn timeCol(table_, "TIME");
+  MEpoch me;
+  if (whichrow > -1) {
+    me = timeCol(uInt(whichrow));
+  } else {
+    Double tm;
+    table_.keywordSet().get("UTC",tm);
+    me = MEpoch(MVEpoch(tm));
+  }
+  return formatTime(me, showdate);
+}
+
+std::string Scantable::getDirectionString(int whichrow) const
+{
+  return formatDirection(getDirection(uInt(whichrow)));
+}
+
+std::vector< double > asap::Scantable::getAbcissa( int whichrow ) const
+{
+  if ( whichrow > int(table_.nrow()) ) throw(AipsError("Illegal ro number"));
+  std::vector<double> stlout;
+  int nchan = specCol_(whichrow).nelements();
+  String us = freqTable_.getUnitString();
+  if ( us == "" || us == "pixel" || us == "channel" ) {
+    for (int i=0; i<nchan; ++i) {
+      stlout.push_back(double(i));
+    }
+    return stlout;
+  }
+
+  const MPosition& mp = getAntennaPosition();
+  const MDirection& md = getDirection(whichrow);
+  const MEpoch& me = timeCol_(whichrow);
+  Double rf = moleculeTable_.getRestFrequency(mmolidCol_(whichrow));
+  SpectralCoordinate spc =
+    freqTable_.getSpectralCoordinate(md, mp, me, rf, mfreqidCol_(whichrow));
+  Vector<Double> pixel(nchan);
+  Vector<Double> world;
+  indgen(pixel);
+  if ( Unit(us) == Unit("Hz") ) {
+    for ( int i=0; i < nchan; ++i) {
+      Double world;
+      spc.toWorld(world, pixel[i]);
+      stlout.push_back(double(world));
+    }
+  } else if ( Unit(us) == Unit("km/s") ) {
+    Vector<Double> world;
+    spc.pixelToVelocity(world, pixel);
+    world.tovector(stlout);
+  }
+  return stlout;
+}
+void asap::Scantable::setDirectionRefString( const std::string & refstr )
+{
+  MDirection::Types mdt;
+  if (refstr != "" && !MDirection::getType(mdt, refstr)) {
+    throw(AipsError("Illegal Direction frame."));
+  }
+  if ( refstr == "" ) {
+    String defaultstr = MDirection::showType(dirCol_.getMeasRef().getType());
+    table_.rwKeywordSet().define("DIRECTIONREF", defaultstr);
+  } else {
+    table_.rwKeywordSet().define("DIRECTIONREF", String(refstr));
+  }
+}
+
+std::string asap::Scantable::getDirectionRefString( ) const
+{
+  return table_.keywordSet().asString("DIRECTIONREF");
+}
+
+MDirection Scantable::getDirection(int whichrow ) const
+{
+  String usertype = table_.keywordSet().asString("DIRECTIONREF");
+  String type = MDirection::showType(dirCol_.getMeasRef().getType());
+  if ( usertype != type ) {
+    MDirection::Types mdt;
+    if (!MDirection::getType(mdt, usertype)) {
+      throw(AipsError("Illegal Direction frame."));
+    }
+    return dirCol_.convert(uInt(whichrow), mdt);
+  } else {
+    return dirCol_(uInt(whichrow));
+  }
+}
+
+std::string Scantable::getAbcissaLabel( int whichrow ) const
+{
+  if ( whichrow > int(table_.nrow()) ) throw(AipsError("Illegal ro number"));
+  const MPosition& mp = getAntennaPosition();
+  const MDirection& md = getDirection(whichrow);
+  const MEpoch& me = timeCol_(whichrow);
+  const Double& rf = mmolidCol_(whichrow);
+  SpectralCoordinate spc =
+    freqTable_.getSpectralCoordinate(md, mp, me, rf, mfreqidCol_(whichrow));
+
+  String s = "Channel";
+  Unit u = Unit(freqTable_.getUnitString());
+  if (u == Unit("km/s")) {
+    s = CoordinateUtil::axisLabel(spc, 0, True,True,  True);
+  } else if (u == Unit("Hz")) {
+    Vector<String> wau(1);wau = u.getName();
+    spc.setWorldAxisUnits(wau);
+    s = CoordinateUtil::axisLabel(spc, 0, True, True, False);
+  }
+  return s;
+
+}
+
+void asap::Scantable::setRestFrequencies( double rf, const std::string& name,
+                                          const std::string& unit )
+{
+  ///@todo lookup in line table to fill in name and formattedname
+  Unit u(unit);
+  Quantum<Double> urf(rf, u);
+  uInt id = moleculeTable_.addEntry(urf.getValue("Hz"), name, "");
+  TableVector<uInt> tabvec(table_, "MOLECULE_ID");
+  tabvec = id;
+}
+
+void asap::Scantable::setRestFrequencies( const std::string& name )
+{
+  throw(AipsError("setRestFrequencies( const std::string& name ) NYI"));
+  ///@todo implement
+}
+
+std::vector< unsigned int > asap::Scantable::rownumbers( ) const
+{
+  std::vector<unsigned int> stlout;
+  Vector<uInt> vec = table_.rowNumbers();
+  vec.tovector(stlout);
+  return stlout;
+}
+
+
+Matrix<Float> asap::Scantable::getPolMatrix( uInt whichrow ) const
+{
+  ROTableRow row(table_);
+  const TableRecord& rec = row.get(whichrow);
+  Table t =
+    originalTable_( originalTable_.col("SCANNO") == Int(rec.asuInt("SCANNO"))
+                    && originalTable_.col("BEAMNO") == Int(rec.asuInt("BEAMNO"))
+                    && originalTable_.col("IFNO") == Int(rec.asuInt("IFNO"))
+                    && originalTable_.col("CYCLENO") == Int(rec.asuInt("CYCLENO")) );
+  ROArrayColumn<Float> speccol(t, "SPECTRA");
+  return speccol.getColumn();
+}
+
+std::vector< std::string > asap::Scantable::columnNames( ) const
+{
+  Vector<String> vec = table_.tableDesc().columnNames();
+  return mathutil::tovectorstring(vec);
+}
+
+casa::MEpoch::Types asap::Scantable::getTimeReference( ) const
+{
+  return MEpoch::castType(timeCol_.getMeasRef().getType());
+}
+
+void Scantable::addFit( const STFitEntry & fit, int row )
+{
+  cout << mfitidCol_(uInt(row)) << endl;
+  uInt id = fitTable_.addEntry(fit, mfitidCol_(uInt(row)));
+  mfitidCol_.put(uInt(row), id);
+}
+
+
+}
+ //namespace asap
Index: tags/Release2.1.0b/src/Scantable.h
===================================================================
--- tags/Release2.1.0b/src/Scantable.h	(revision 1196)
+++ tags/Release2.1.0b/src/Scantable.h	(revision 1196)
@@ -0,0 +1,474 @@
+//
+// C++ Interface: Scantable
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2005
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPSCANTABLE_H
+#define ASAPSCANTABLE_H
+
+// STL
+#include <string>
+#include <vector>
+// AIPS++
+#include <casa/aips.h>
+#include <casa/Arrays/MaskedArray.h>
+#include <casa/BasicSL/String.h>
+#include <casa/Utilities/CountedPtr.h>
+
+#include <coordinates/Coordinates/SpectralCoordinate.h>
+
+#include <tables/Tables/Table.h>
+#include <tables/Tables/ArrayColumn.h>
+#include <tables/Tables/ScalarColumn.h>
+
+#include <measures/TableMeasures/ScalarMeasColumn.h>
+
+#include "Logger.h"
+#include "STHeader.h"
+#include "STFrequencies.h"
+#include "STWeather.h"
+#include "STFocus.h"
+#include "STTcal.h"
+#include "STMolecules.h"
+#include "STSelector.h"
+#include "STHistory.h"
+#include "STPol.h"
+#include "STFit.h"
+#include "STFitEntry.h"
+
+namespace asap {
+
+/**
+  * This class contains and wraps a casa::Table, which is used to store
+  * all the information. This can be either a MemoryTable or a
+  * disk based Table.
+  * It provides access functions to the underlying table
+  * It contains n subtables:
+  * @li weather
+  * @li frequencies
+  * @li molecules
+  * @li tcal
+  * @li focus
+  * @li fits
+  *
+  * @brief The main ASAP data container
+  * @author Malte Marquarding
+  * @date
+  * @version
+*/
+class Scantable : private Logger
+{
+
+friend class STMath;
+
+public:
+  /**
+   * Default constructor
+   */
+  Scantable(casa::Table::TableType ttype = casa::Table::Memory);
+
+  /**
+   * Create a Scantable object form an existing table on disk
+   * @param[in] name the name of the existing Scantable
+   */
+  Scantable(const std::string& name, casa::Table::TableType ttype = casa::Table::Memory);
+
+  /// @fixme this is only sensible for MemoryTables....
+  Scantable(const Scantable& other, bool clear=true);
+
+  /**
+   * Destructor
+   */
+  virtual ~Scantable();
+
+  /**
+   * get a const reference to the underlying casa::Table
+   * @return const \ref casa::Table reference
+   */
+  const casa::Table& table() const;
+
+  /**
+   * get a reference to the underlying casa::Table with the Selection
+   * object applied if set
+   * @return casa::Table reference
+   */
+  casa::Table& table();
+
+
+  /**
+   * Get a handle to the selection object
+   * @return constant STSelector reference
+   */
+  const STSelector& getSelection() const { return selector_; }
+
+  /**
+   * Set the data to be a subset as defined by the STSelector
+   * @param selection a STSelector object
+   */
+  void setSelection(const STSelector& selection);
+
+  /**
+   * unset the selection of the data
+   */
+  void unsetSelection();
+  /**
+   * set the header
+   * @param[in] sdh an STHeader object
+   */
+  void setHeader( const STHeader& sth );
+
+  /**
+   * get the header information
+   * @return an STHeader object
+   */
+  STHeader getHeader( ) const;
+
+  /**
+   * Checks if the "other" Scantable is conformant with this,
+   * i.e. if  header values are the same.
+   * @param[in] other another Scantable
+   * @return true or false
+   */
+  bool conformant( const Scantable& other);
+
+  /**
+   *
+   * @param stype The type of the source, 0 = on, 1 = off
+   */
+  void setSourceType(int stype);
+
+
+  /**
+   * The number of scans in the table
+   * @return number of scans in the table
+   */
+  int nscan() const;
+
+  casa::MEpoch::Types getTimeReference() const;
+
+  /**
+   * Get global antenna position
+   * @return casa::MPosition
+   */
+  casa::MPosition getAntennaPosition() const;
+
+	/**
+	 * the @ref casa::MDirection for a specific row
+	 * @param[in] whichrow the row number
+	 * return casa::MDirection
+	 */
+  casa::MDirection getDirection( int whichrow ) const;
+
+	/**
+	 * get the direction type as a string, e.g. "J2000"
+	 * @param[in] whichrow the row number
+	 * return the direction string
+	 */
+  std::string getDirectionString( int whichrow ) const;
+
+	/**
+	 * set the direction type as a string, e.g. "J2000"
+	 * @param[in] the direction type
+	 */
+  void setDirectionRefString(const std::string& refstr="");
+  /**
+   * get the direction reference string
+   * @return a string describing the direction reference
+   */
+  std::string getDirectionRefString() const;	/**
+	 * get the direction type as a string, e.g. "J2000"
+	 * param[in] whichrow the row number
+	 * return the direction string
+	 */
+
+
+  /**
+   *  Return the Flux unit of the data, e.g. "Jy" or "K"
+   * @return string
+   */
+  std::string getFluxUnit() const;
+
+  /**
+   * Set the Flux unit of the data
+   * @param unit a string representing the unit, e.g "Jy" or "K"
+   */
+  void setFluxUnit( const std::string& unit );
+
+  /**
+   * Set the Stokes type of the data
+   * @param a string representing the type, e.g "circular" or "linear"
+   */
+  void setFeedType( const std::string& feedtype );
+
+  /**
+   *
+   * @param instrument a string representing an insturment. see xxx
+   */
+  void setInstrument( const std::string& instrument );
+
+  /**
+   * (Re)calculate the azimuth and elevationnfor all rows
+   */
+  void calculateAZEL();
+
+  /**
+   * "hard" flag the data, this flags everything selected in setSelection()
+   * param[in] msk a boolean mask of length nchan describing the points to
+   * to be flagged
+   */
+  void flag( const std::vector<bool>& msk = std::vector<bool>());
+
+  /**
+   * Return a list of row numbers with respect to the original table.
+   * @return a list of unsigned ints
+   */
+  std::vector<unsigned int> rownumbers() const;
+
+
+  /**
+   * Get the number of beams in the data or a specific scan
+   * @param scanno the scan number to get the number of beams for.
+   * If scanno<0 the number is retrieved from the header.
+   * @return an integer number
+   */
+  int nbeam(int scanno=-1) const;
+  /**
+   * Get the number of IFs in the data or a specific scan
+   * @param scanno the scan number to get the number of IFs for.
+   * If scanno<0 the number is retrieved from the header.
+   * @return an integer number
+   */
+  int nif(int scanno=-1) const;
+  /**
+   * Get the number of polarizations in the data or a specific scan
+   * @param scanno the scan number to get the number of polarizations for.
+   * If scanno<0 the number is retrieved from the header.
+   * @return an integer number
+   */
+  int npol(int scanno=-1) const;
+
+  std::string getPolType() const;
+
+
+  /**
+   * Get the number of integartion cycles
+   * @param scanno the scan number to get the number of rows for.
+   * If scanno<0 the number is retrieved from the header.
+   * @return the number of rows (for the specified scanno)
+   */
+  int nrow(int scanno=-1) const;
+
+  int getBeam(int whichrow) const;
+  std::vector<uint> getBeamNos() { return getNumbers(beamCol_); }
+
+  int getIF(int whichrow) const;
+  std::vector<uint> getIFNos() { return getNumbers(ifCol_); }
+
+  int getPol(int whichrow) const;
+  std::vector<uint> getPolNos() { return getNumbers(polCol_); }
+
+  std::vector<uint> getScanNos() { return getNumbers(scanCol_); }
+  int getScan(int whichrow) const { return scanCol_(whichrow); }
+
+  /**
+   * Get the number of channels in the data or a specific IF. This currently
+   * varies only with IF number
+   * @param ifno the IF number to get the number of channels for.
+   * If ifno<0 the number is retrieved from the header.
+   * @return an integer number
+   */
+  int nchan(int ifno=-1) const;
+  int getChannels(int whichrow) const;
+
+  int ncycle(int scanno=-1) const;
+  int getCycle(int whichrow) const { return cycleCol_(whichrow); }
+
+  double getInterval(int whichrow) const
+    { return integrCol_(whichrow); }
+
+  float getTsys(int whichrow) const
+    { return casa::Vector<casa::Float>(tsysCol_(whichrow))(0); }
+  float getElevation(int whichrow) const
+    { return elCol_(whichrow); }
+  float getAzimuth(int whichrow) const
+    { return azCol_(whichrow); }
+  float getParAngle(int whichrow) const
+    { return paraCol_(whichrow); }
+
+  std::string getSourceName(int whichrow) const
+    { return srcnCol_(whichrow); }
+
+  std::vector<bool> getMask(int whichrow) const;
+  std::vector<float> getSpectrum(int whichrow,
+                                 const std::string& poltype = "" ) const;
+
+  void setSpectrum(const std::vector<float>& spec, int whichrow);
+
+  std::string getPolarizationLabel(int index, const std::string& ptype) const
+    { return STPol::getPolLabel(index, ptype ); }
+
+  /**
+   * Write the Scantable to disk
+   * @param filename the output file name
+   */
+  void makePersistent(const std::string& filename);
+
+  std::vector<std::string> getHistory() const
+    { return historyTable_.getHistory(); };
+
+  void addHistory(const std::string& hist) { historyTable_.addEntry(hist); }
+
+  void appendToHistoryTable(const STHistory& otherhist)
+    { historyTable_.append(otherhist); }
+
+  std::string summary(bool verbose=false);
+  std::string getTime(int whichrow=-1, bool showdate=true) const;
+
+  // returns unit, conversion frame, doppler, base-frame
+
+  /**
+   * Get the frequency set up
+   * This is forwarded to the STFrequencies subtable
+   * @return unit, frame, doppler
+   */
+  std::vector<std::string> getCoordInfo() const
+    { return freqTable_.getInfo(); };
+
+  void setCoordInfo(std::vector<string> theinfo)
+    { return freqTable_.setInfo(theinfo); };
+
+
+  std::vector<double> getAbcissa(int whichrow) const;
+
+  std::string getAbcissaLabel(int whichrow) const;
+  std::vector<double> getRestFrequencies() const
+    { return moleculeTable_.getRestFrequencies(); }
+
+  void setRestFrequencies(double rf, const std::string& name = "",
+                          const std::string& = "Hz");
+  void setRestFrequencies(const std::string& name);
+
+  void convertDirection(const std::string& newframe);
+
+  STFrequencies& frequencies() { return freqTable_; }
+  STWeather& weather() { return weatherTable_; }
+  STFocus& focus() { return focusTable_; }
+  STTcal& tcal() { return tcalTable_; }
+  STMolecules& molecules() { return moleculeTable_; }
+  STHistory& history() { return historyTable_; }
+  STFit& fit() { return fitTable_; }
+
+  std::vector<std::string> columnNames() const;
+
+  void addFit(const STFitEntry& fit, int row);
+  STFitEntry getFit(int row) const
+    { STFitEntry fe; fitTable_.getEntry(fe, mfitidCol_(row)); return fe; }
+
+private:
+
+  casa::Matrix<casa::Float> getPolMatrix( casa::uInt whichrow ) const;
+
+  /**
+   * Turns a time vale into a formatted string
+   * @param x
+   * @return
+   */
+  std::string formatSec(casa::Double x) const;
+
+  std::string formatTime(const casa::MEpoch& me, bool showdate)const;
+
+  /**
+   *  Turns a casa::MDirection into a nicely formatted string
+   * @param md an casa::MDirection
+   * @return
+   */
+  std::string formatDirection(const casa::MDirection& md) const;
+
+
+  /**
+   * Create a unique file name for the paged (temporary) table
+   * @return just the name
+   */
+  static casa::String generateName();
+
+  /**
+   * attach to cached columns
+   */
+  void attach();
+
+  /**
+   * Set up the main casa::Table
+   */
+  void setupMainTable();
+
+  void attachSubtables();
+  void copySubtables(const Scantable& other);
+
+  /**
+   * Convert an "old" asap1 style row index into a new index
+   * @param[in] therow
+   * @return and index into @table_
+   */
+  int rowToScanIndex(int therow);
+
+  std::vector<uint> getNumbers(casa::ScalarColumn<casa::uInt>& col);
+
+  static const casa::uInt version_ = 2;
+
+  STSelector selector_;
+
+  casa::Table::TableType type_;
+
+  // the actual data
+  casa::Table table_;
+  casa::Table originalTable_;
+
+  STTcal tcalTable_;
+  STFrequencies freqTable_;
+  STWeather weatherTable_;
+  STFocus focusTable_;
+  STMolecules moleculeTable_;
+  STHistory historyTable_;
+  STFit fitTable_;
+
+  // Cached Columns to avoid reconstructing them for each row get/put
+  casa::ScalarColumn<casa::Double> integrCol_;
+  casa::MDirection::ScalarColumn dirCol_;
+  casa::MEpoch::ScalarColumn timeCol_;
+  casa::ScalarColumn<casa::Float> azCol_;
+  casa::ScalarColumn<casa::Float> elCol_;
+  casa::ScalarColumn<casa::Float> paraCol_;
+  casa::ScalarColumn<casa::String> srcnCol_, fldnCol_;
+  casa::ScalarColumn<casa::uInt> scanCol_, beamCol_, ifCol_, polCol_, cycleCol_;
+  casa::ScalarColumn<casa::Int> rbeamCol_, srctCol_;
+  casa::ArrayColumn<casa::Float> specCol_, tsysCol_;
+  casa::ArrayColumn<casa::uChar> flagsCol_;
+
+  // id in frequencies table
+  casa::ScalarColumn<casa::uInt> mfreqidCol_;
+  // id in tcal table
+  casa::ScalarColumn<casa::uInt> mtcalidCol_;
+
+  casa::ArrayColumn<casa::String> histitemCol_;
+  casa::ScalarColumn<casa::Int> mfitidCol_;
+  casa::ScalarColumn<casa::uInt> mweatheridCol_;
+
+  casa::ScalarColumn<casa::uInt> mfocusidCol_;
+
+  casa::ScalarColumn<casa::uInt> mmolidCol_;
+
+  static std::map<std::string, STPol::STPolFactory *> factories_;
+  void initFactories();
+
+};
+
+
+} // namespace
+
+#endif
Index: tags/Release2.1.0b/src/ScantableWrapper.h
===================================================================
--- tags/Release2.1.0b/src/ScantableWrapper.h	(revision 1196)
+++ tags/Release2.1.0b/src/ScantableWrapper.h	(revision 1196)
@@ -0,0 +1,201 @@
+//
+// C++ Interface: Scantable
+//
+// Description:
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#ifndef ASAPSCANTABLEWRAPPER_H
+#define ASAPSCANTABLEWRAPPER_H
+
+#include <vector>
+#include <string>
+#include <casa/Arrays/Vector.h>
+
+#include "MathUtils.h"
+#include "STFit.h"
+#include "Scantable.h"
+
+namespace asap {
+/**
+  * This class contains and wraps a CountedPtr<Scantable>, as the CountedPtr
+  * class does not provide the dor operator which is need for references
+  * in boost::python
+  * see Scantable for interfce description
+  *
+  * @brief The main ASAP data container wrapper
+  * @author Malte Marquarding
+  * @date 2006-02-23
+  * @version 2.0a
+*/
+class ScantableWrapper {
+
+public:
+  ScantableWrapper( const std::string& name,
+                    int type=0)
+  {
+    casa::Table::TableType tp = casa::Table::Memory;
+    if ( type == 1 ) tp = casa::Table::Plain;
+    table_ = new Scantable(name, tp);
+  }
+
+  ScantableWrapper(int type=0)
+  {
+    casa::Table::TableType tp = casa::Table::Memory;
+    if ( type == 1) tp = casa::Table::Plain;
+    table_= new Scantable(tp);
+  }
+
+  ScantableWrapper(casa::CountedPtr<Scantable> cp) : table_(cp) {;}
+
+  ScantableWrapper(const ScantableWrapper& mt) :
+    table_(mt.getCP()) {;}
+
+  void assign(const ScantableWrapper& mt)
+    { table_= mt.getCP(); }
+
+  ScantableWrapper copy() {
+    return ScantableWrapper(new Scantable(*(this->getCP()), false));
+  }
+
+  std::vector<float> getSpectrum( int whichrow=0,
+                                  const std::string& poltype="" ) const {
+    return table_->getSpectrum(whichrow, poltype);
+  }
+  //  std::string getPolarizationLabel(bool linear, bool stokes, bool linPol, int polIdx) const {
+  // Boost fails with 4 arguments.
+  std::string getPolarizationLabel(int index, const std::string& ptype) const {
+    return table_->getPolarizationLabel(index, ptype);
+  }
+
+  std::vector<double> getAbcissa(int whichrow=0) const
+    { return table_->getAbcissa(whichrow); }
+
+  std::string getAbcissaLabel(int whichrow=0) const
+    { return table_->getAbcissaLabel(whichrow); }
+
+  float getTsys(int whichrow=0) const
+    { return table_->getTsys(whichrow); }
+
+  std::string getTime(int whichrow=0) const
+    { return table_->getTime(whichrow); }
+
+  std::string getDirectionString(int whichrow=0) const
+    { return table_->getDirectionString(whichrow); }
+
+  std::string getFluxUnit() const { return table_->getFluxUnit(); }
+
+  void setFluxUnit(const std::string& unit) { table_->setFluxUnit(unit); }
+
+  void setInstrument(const std::string& name) {table_->setInstrument(name);}
+  void setFeedType(const std::string& ftype) {table_->setFeedType(ftype);}
+
+  std::vector<bool> getMask(int whichrow=0) const
+    { return table_->getMask(whichrow); }
+
+  void flag(const std::vector<bool>& msk=std::vector<bool>())
+    { table_->flag(msk); }
+
+  std::string getSourceName(int whichrow=0) const
+    { return table_->getSourceName(whichrow); }
+
+  float getElevation(int whichrow=0) const
+    { return table_->getElevation(whichrow); }
+
+  float getAzimuth(int whichrow=0) const
+    { return table_->getAzimuth(whichrow); }
+
+  float getParAngle(int whichrow=0) const
+    { return table_->getParAngle(whichrow); }
+
+
+  void setSpectrum(std::vector<float> spectrum, int whichrow=0)
+    { table_->setSpectrum(spectrum, whichrow); }
+
+  std::vector<uint> getIFNos() { return table_->getIFNos(); }
+  int getIF(int whichrow) const {return table_->getIF(whichrow);}
+  std::vector<uint> getBeamNos() { return table_->getBeamNos(); }
+  int getBeam(int whichrow) const {return table_->getBeam(whichrow);}
+  std::vector<uint> getPolNos() { return table_->getPolNos(); }
+  int getPol(int whichrow) const {return table_->getPol(whichrow);}
+  int getCycle(int whichrow) const {return table_->getCycle(whichrow);}
+  std::vector<uint> getScanNos() { return table_->getScanNos(); }
+  int getScan(int whichrow) const {return table_->getScan(whichrow);}
+
+  STSelector getSelection() const { return table_->getSelection(); }
+  void setSelection(const STSelector& sts)
+    { return table_->setSelection(sts);}
+
+  std::string getPolType() const { return table_->getPolType(); }
+
+  int nif(int scanno=-1) const {return table_->nif(scanno);}
+  int nbeam(int scanno=-1) const {return table_->nbeam(scanno);}
+  int npol(int scanno=-1) const {return table_->npol(scanno);}
+  int nchan(int ifno=-1) const {return table_->nchan(ifno);}
+  int nscan() const {return table_->nscan();}
+  int nrow() const {return table_->nrow();}
+  int ncycle(int scanno) const {return table_->ncycle(scanno);}
+
+  void makePersistent(const std::string& fname)
+    { table_->makePersistent(fname); }
+
+  void setSourceType(int stype)
+    { table_->setSourceType(stype); }
+
+  void setRestFrequencies(double rf, const std::string& name,
+                          const std::string& unit)
+    { table_->setRestFrequencies(rf, name, unit); }
+/*
+  void setRestFrequencies(const std::string& name) {
+    table_->setRestFrequencies(name);
+  }
+*/
+
+  std::vector<double> getRestFrequencies() const
+    { return table_->getRestFrequencies(); }
+
+  void setCoordInfo(std::vector<string> theinfo) {
+    table_->setCoordInfo(theinfo);
+  }
+  std::vector<string> getCoordInfo() const {
+    return table_->getCoordInfo();
+  }
+
+  void setDirection(const std::string& refstr="")
+    { table_->setDirectionRefString(refstr); }
+
+  casa::CountedPtr<Scantable> getCP() const {return table_;}
+  Scantable* getPtr() {return &(*table_);}
+
+  std::string summary(bool verbose=false) const {
+    return table_->summary(verbose);
+  }
+
+  std::vector<std::string> getHistory()const
+    { return table_->getHistory(); }
+
+  void addHistory(const std::string& hist)
+    { table_->addHistory(hist); }
+
+  void addFit(const STFitEntry& fit, int row)
+    { table_->addFit(fit, row); }
+
+  STFitEntry getFit(int whichrow) const
+  { return table_->getFit(whichrow); }
+
+  void calculateAZEL() { table_->calculateAZEL(); };
+
+  std::vector<std::string> columnNames() const
+    { return table_->columnNames(); }
+
+private:
+  casa::CountedPtr<Scantable> table_;
+};
+
+} // namespace
+#endif
+
Index: tags/Release2.1.0b/src/Templates.cpp
===================================================================
--- tags/Release2.1.0b/src/Templates.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/Templates.cpp	(revision 1196)
@@ -0,0 +1,116 @@
+//#---------------------------------------------------------------------------
+//# Templates.cc: explicit templates for aips++
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+#include "Scantable.h"
+
+#include <casa/aips.h>
+#include <casa/namespace.h>
+#include <casa/Exceptions/Error.cc>
+#include <casa/Utilities/CountedPtr.cc>
+
+template class casa::CountedConstPtr<asap::Scantable>;
+template class casa::CountedPtr<asap::Scantable>;
+template class casa::PtrRep<asap::Scantable>;
+template class casa::SimpleCountedConstPtr<asap::Scantable>;
+template class casa::SimpleCountedPtr<asap::Scantable>;
+
+#include <casa/Arrays/ArrayLogical.cc>
+#include <casa/Arrays/ArrayMath.cc>
+#include <casa/Arrays/MaskArrMath.cc>
+#include <casa/Arrays/MaskedArray.cc>
+#include <casa/Arrays/Array.h>
+#include <casa/Arrays/Array.cc>
+#include <casa/Arrays/Vector.h>
+#include <casa/Utilities/BinarySearch.cc>
+#include <coordinates/Coordinates/FrequencyAligner.cc>
+#include <lattices/Lattices/Lattice.h>
+#include <lattices/Lattices/LatticeUtilities.cc>
+#include <scimath/Mathematics/InterpolateArray1D.cc>
+#include <tables/Tables/BaseMappedArrayEngine.cc>
+#include <tables/Tables/TableVector.h>
+#include <tables/Tables/TVec.cc>
+#include <tables/Tables/TableVector.cc>
+#include <tables/Tables/TVecScaCol.cc>
+#include <tables/Tables/TVecTemp.cc>
+//#include <tables/Tables/TVecMath.h>
+
+template class ROTableVector<uInt>;
+template class TableVector<uInt>;
+template class TabVecScaCol<uInt>;
+template class TabVecTemp<uInt>;
+template class TabVecRep<uInt>;
+
+template class ROTableVector<Float>;
+template class TabVecScaCol<Float>;
+template class TabVecTemp<Float>;
+template class TabVecRep<Float>;
+
+template class ROTableVector<uChar>;
+template class TableVector<uChar>;
+template class TabVecScaCol<uChar>;
+template class TabVecTemp<uChar>;
+template class TabVecRep<uChar>;
+
+template void convertArray<Bool, uChar>(Array<Bool> &, Array<uChar> const &);
+template void convertArray<uChar, Bool>(Array<uChar> &, Array<Bool> const &);
+
+template Array<Float>& operator/=<Float>(Array<Float>&, MaskedArray<Float> const&);
+template MaskedArray<Float> const& operator*=<Float>(MaskedArray<Float> const&, Float const&);
+template MaskedArray<Float> const& operator*=<Float>(MaskedArray<Float> const&, Array<Float> const&);
+template MaskedArray<Float> const& operator/=<Float>(MaskedArray<Float> const&, Float const&);
+template MaskedArray<Float> operator+<Float>(MaskedArray<Float> const&, MaskedArray<Float> const&);
+template MaskedArray<Float> operator-<Float>(MaskedArray<Float> const&, MaskedArray<Float> const&);
+template MaskedArray<Float> operator-<Float>(MaskedArray<Float> const&, Array<Float> const&);
+
+template MaskedArray<Float> operator/<Float>(MaskedArray<Float> const&, MaskedArray<Float> const&);
+
+template MaskedArray<Float> operator*<Float>(MaskedArray<Float> const&, MaskedArray<Float> const&);
+template MaskedArray<Float> operator*<Float>(MaskedArray<Float> const&, Array<Float> const&);
+template MaskedArray<Float> operator*<Float>(Array<Float> const&, MaskedArray<Float> const&);
+template MaskedArray<Float> operator*<Float>(Float const&, MaskedArray<Float> const&);
+template Float stddev<Float>(MaskedArray<Float> const&);
+template Float median<Float>(MaskedArray<Float> const&, Bool, Bool);
+template Float sumsquares<Float>(MaskedArray<Float> const&);
+template Float avdev<Float>(MaskedArray<Float> const&);
+
+template void LatticeUtilities::bin(MaskedArray<float>&, MaskedArray<float> const&, uInt, uInt);
+
+template class FrequencyAligner<Float>;
+
+template class BaseMappedArrayEngine<Float, Float>;
+
+#include "MathUtils2.cpp"
+namespace mathutil {
+  template void hanning(Vector<Float>&, Vector<Bool>&,
+			const Vector<Float>&,
+			const Vector<Bool>&,
+			Bool, Bool);
+}
+
Index: tags/Release2.1.0b/src/pyconversions.h
===================================================================
--- tags/Release2.1.0b/src/pyconversions.h	(revision 1196)
+++ tags/Release2.1.0b/src/pyconversions.h	(revision 1196)
@@ -0,0 +1,254 @@
+/* -*- mode:c++ -*- */
+
+/** @file
+ 
+Boost.Python conversion functions from STL containers to Python
+sequences and vice versa.
+
+$Id$
+
+*/
+
+#ifndef PY_CONVERSIONS_H
+#define PY_CONVERSIONS_H
+
+#include <vector>
+
+#include <boost/python.hpp>
+
+using namespace boost::python;
+
+/** A wrapper of a conversion function to convert a STL vector to a
+    Python tuple.  This class satisfies the requirements of the
+    boost::python::to_python_converter conversion template argument.
+
+    Copied from
+    scitbx/include/scitbx/boost_python/container_conversions.h that is
+    described in the <a
+    href="http://www.boost.org/libs/python/doc/v2/faq.html">
+    Boost.Python FAQ. </a>
+
+    @author Ralf W. Grosse-Kunstleve <rwgk@yahoo.com> of 
+    <a href="http://www.lbl.gov/">Lawrence Berkeley National Laboratory</a>
+*/
+  template < typename ContainerType >
+  struct to_tuple
+  {
+    /** Creates and returns a Python @c tuple from the elements copied
+	from a STL container. The @c ContainerType must be a vector,
+	but may contain any type of object supported by the
+	boost::python::object constructor. */
+    static PyObject* convert (ContainerType const& c)
+    {
+      using boost::python::incref; // works around gcc 2.96 bug
+      using boost::python::list; // dito 
+      list result;
+      typename ContainerType::const_iterator i = c.begin();
+      for( ; i != c.end(); ++i)
+	{
+	  result.append(*i);
+	}
+      return incref(tuple(result).ptr());
+    }
+  };
+
+/** Converts an STL vector of T objects to Python tuple. 
+
+    Copied from
+    scitbx/include/scitbx/boost_python/container_conversions.h that is
+    described in the <a
+    href="http://www.boost.org/libs/python/doc/v2/faq.html">
+    Boost.Python FAQ. </a>
+
+    @author Ralf W. Grosse-Kunstleve <rwgk@yahoo.com> of
+    <a href="http://www.lbl.gov/">Lawrence Berkeley National Laboratory</a>
+*/
+  template < typename T >
+  struct std_vector_to_tuple 
+  {
+    std_vector_to_tuple ()
+    {
+      to_python_converter < std::vector < T >, 
+	                    to_tuple < std::vector < T > >  > ();
+    }
+  };
+
+// /** Interface to AxesType enumeration. */
+// struct AxesOwner
+// {
+//   public:
+//     AxesOwner(){};
+//     typedef AxesType enum_type;
+// };
+
+// /** Conversion of AxesType enumeration. */
+// struct EnumTypeConverters
+//   : python::enum_as_int_converters <AxesOwner::enum_type>
+// {
+// };
+
+/** Default operations on all containers for conversion from Python
+    container to C++ one.
+
+    Copied from
+    scitbx/include/scitbx/boost_python/container_conversions.h that is
+    described in the <a
+    href="http://www.boost.org/libs/python/doc/v2/faq.html">
+    Boost.Python FAQ. </a>
+
+    @author Ralf W. Grosse-Kunstleve <rwgk@yahoo.com> of
+    <a href="http://www.lbl.gov/">Lawrence Berkeley National Laboratory</a>
+*/
+  struct default_policy
+  {
+    static bool check_convertibility_per_element() { return false; }
+
+    template <typename ContainerType>
+    static bool check_size(boost::type<ContainerType>, std::size_t sz)
+    {
+      return true;
+    }
+
+    template <typename ContainerType>
+    static void assert_size(boost::type<ContainerType>, std::size_t sz) {}
+
+    template <typename ContainerType>
+    static void reserve(ContainerType& a, std::size_t sz) {}
+  };
+
+/** Operations on containers that have variable capacity for
+    conversion from Python container to C++ one.
+
+    Copied from
+    scitbx/include/scitbx/boost_python/container_conversions.h that is
+    described in the <a
+    href="http://www.boost.org/libs/python/doc/v2/faq.html">
+    Boost.Python FAQ. </a>
+
+    @author Ralf W. Grosse-Kunstleve <rwgk@yahoo.com> of
+    <a href="http://www.lbl.gov/">Lawrence Berkeley National Laboratory</a>
+*/
+  struct variable_capacity_policy : default_policy
+  {
+    template <typename ContainerType>
+    static void reserve(ContainerType& a, std::size_t sz)
+    {
+      a.reserve(sz);
+    }
+
+    template <typename ContainerType, typename ValueType>
+    static void set_value(ContainerType& a, std::size_t i, ValueType const& v)
+    {
+      assert(a.size() == i);
+      a.push_back(v);
+    }
+  };
+
+/** Conversion of Python sequence to C++ container.
+
+    Copied from
+    scitbx/include/scitbx/boost_python/container_conversions.h that is
+    described in the <a
+    href="http://www.boost.org/libs/python/doc/v2/faq.html">
+    Boost.Python FAQ. </a>
+
+    @author Ralf W. Grosse-Kunstleve <rwgk@yahoo.com> of
+    <a href="http://www.lbl.gov/">Lawrence Berkeley National Laboratory</a>
+*/
+  template <typename ContainerType, typename ConversionPolicy>
+  struct from_python_sequence
+  {
+    typedef typename ContainerType::value_type container_element_type;
+
+    from_python_sequence()
+    {
+      boost::python::converter::registry::push_back(
+        &convertible,
+        &construct,
+        boost::python::type_id<ContainerType>());
+    }
+
+    /** Appears to return @a obj_ptr if it is type of Python sequence
+	that can be convertible to C++ container. */
+    static void* convertible(PyObject* obj_ptr)
+    {
+      using namespace boost::python;
+      using boost::python::allow_null; // works around gcc 2.96 bug
+      {
+        // Restriction to list, tuple, iter, xrange until
+        // Boost.Python overload resolution is enhanced.
+         //
+         // add PySequence_Check() for numarray.
+         //
+         if (!(   PyList_Check(obj_ptr)
+                  || PyTuple_Check(obj_ptr)
+                  || PyIter_Check(obj_ptr)
+                  || PyRange_Check(obj_ptr)
+                  || PySequence_Check(obj_ptr) )) return 0;
+      }
+      handle<> obj_iter(allow_null(PyObject_GetIter(obj_ptr)));
+      if (!obj_iter.get()) { // must be convertible to an iterator
+        PyErr_Clear();
+        return 0;
+      }
+      if (ConversionPolicy::check_convertibility_per_element()) {
+        int obj_size = PyObject_Length(obj_ptr);
+        if (obj_size < 0) { // must be a measurable sequence
+          PyErr_Clear();
+          return 0;
+        }
+        if (!ConversionPolicy::check_size(
+          boost::type<ContainerType>(), obj_size)) return 0;
+        bool is_range = PyRange_Check(obj_ptr);
+        //std::size_t i=0;
+		int i = 0;
+#ifndef _MSC_VER // because it causes c1001: internal compiler error
+        for(;;i++) {
+          handle<> py_elem_hdl(allow_null(PyIter_Next(obj_iter.get())));
+          if (PyErr_Occurred()) {
+            PyErr_Clear();
+            return 0;
+          }
+          if (!py_elem_hdl.get()) break; // end of iteration
+          object py_elem_obj(py_elem_hdl);
+          extract<container_element_type> elem_proxy(py_elem_obj);
+          if (!elem_proxy.check()) return 0;
+          if (is_range) break; // in a range all elements are of the same type
+        }
+        if (!is_range) assert(i == obj_size );
+#endif
+      }
+      return obj_ptr;
+    }
+
+    /** Constructs a C++ container from a Python sequence. */
+    static void construct(
+      PyObject* obj_ptr,
+      boost::python::converter::rvalue_from_python_stage1_data* data)
+    {
+      using namespace boost::python;
+      using boost::python::allow_null; // works around gcc 2.96 bug
+      using boost::python::converter::rvalue_from_python_storage; // dito
+      using boost::python::throw_error_already_set; // dito
+      handle<> obj_iter(PyObject_GetIter(obj_ptr));
+      void* storage = (
+        (rvalue_from_python_storage<ContainerType>*)
+          data)->storage.bytes;
+      new (storage) ContainerType();
+      data->convertible = storage;
+      ContainerType& result = *((ContainerType*)storage);
+      std::size_t i=0;
+      for(;;i++) {
+        handle<> py_elem_hdl(allow_null(PyIter_Next(obj_iter.get())));
+        if (PyErr_Occurred()) throw_error_already_set();
+        if (!py_elem_hdl.get()) break; // end of iteration
+        object py_elem_obj(py_elem_hdl);
+        extract<container_element_type> elem_proxy(py_elem_obj);
+        ConversionPolicy::set_value(result, i, elem_proxy());
+      }
+      ConversionPolicy::assert_size(boost::type<ContainerType>(), i);
+    }
+
+  };
+
+#endif // PY_CONVERSIONS_H
Index: tags/Release2.1.0b/src/python_Fitter.cpp
===================================================================
--- tags/Release2.1.0b/src/python_Fitter.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/python_Fitter.cpp	(revision 1196)
@@ -0,0 +1,62 @@
+//#---------------------------------------------------------------------------
+//# python_Fitter.cc: python exposure of c++ Fitter class
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+#include <boost/python.hpp>
+
+#include "STFitter.h"
+
+using namespace boost::python;
+
+namespace asap {
+  namespace python {
+
+    void python_Fitter() {
+      class_<Fitter>("fitter")
+        .def( init <> () )
+        .def("setexpression", &Fitter::setExpression)
+        .def("setdata", &Fitter::setData)
+        .def("getresidual", &Fitter::getResidual)
+        .def("getfit", &Fitter::getFit)
+        .def("getfixedparameters", &Fitter::getFixedParameters)
+        .def("setfixedparameters", &Fitter::setFixedParameters)
+        .def("getparameters", &Fitter::getParameters)
+        .def("setparameters", &Fitter::setParameters)
+        .def("getestimate", &Fitter::getEstimate)
+        .def("estimate", &Fitter::computeEstimate)
+        .def("geterrors", &Fitter::getErrors)
+        .def("getchi2", &Fitter::getChisquared)
+        .def("reset", &Fitter::reset)
+        .def("fit", &Fitter::fit)
+        .def("evaluate", &Fitter::evaluate)
+      ;
+    };
+
+  } //namespace python
+} // namespace asap
Index: tags/Release2.1.0b/src/python_LineCatalog.cpp
===================================================================
--- tags/Release2.1.0b/src/python_LineCatalog.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/python_LineCatalog.cpp	(revision 1196)
@@ -0,0 +1,39 @@
+//
+// C++ Implementation: python_LineCatalog
+//
+// Description: This file is the boost::python wrapper for asap::LineCatalog
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+
+#include <string>
+
+#include <boost/python.hpp>
+
+#include "LineCatalog.h"
+
+using namespace boost::python;
+
+namespace asap {
+  namespace python {
+
+    void python_LineCatalog() {
+      class_<LineCatalog>("linecatalog")
+        .def( init < const std::string& > () )
+        .def("summary", &LineCatalog::summary)
+        .def("get_name", &LineCatalog::getName)
+        .def("get_frequency", &LineCatalog::getFrequency)
+        .def("set_frequency_limits", &LineCatalog::setFrequencyLimits)
+        .def("set_strength_limits", &LineCatalog::setStrengthLimits)
+        .def("set_name", &LineCatalog::setPattern)
+        .def("save", &LineCatalog::save)
+        .def("reset", &LineCatalog::reset)
+        .def("nrow", &LineCatalog::nrow)
+     ;
+    };
+  }
+}
Index: tags/Release2.1.0b/src/python_Logger.cpp
===================================================================
--- tags/Release2.1.0b/src/python_Logger.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/python_Logger.cpp	(revision 1196)
@@ -0,0 +1,52 @@
+//#---------------------------------------------------------------------------
+//# python_Logger.cc: python exposure of c++ Logger class
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id:
+//#---------------------------------------------------------------------------
+#include <boost/python.hpp>
+
+#include "Logger.h"
+
+using namespace boost::python;
+
+namespace asap {
+  namespace python {
+    void python_Logger() {
+      class_<Logger>("Log")
+        .def( init <> () )
+        //.def( init <bool> () )
+	.def("push", &Logger::pushLog,
+         (boost::python::arg("newline")=1))
+	.def("pop", &Logger::popLog)
+	.def("disable", &Logger::disableLog)
+	.def("enable", &Logger::enableLog)
+      ;
+    };
+
+  } //namespace python
+} // namespace asap
Index: tags/Release2.1.0b/src/python_STFiller.cpp
===================================================================
--- tags/Release2.1.0b/src/python_STFiller.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/python_STFiller.cpp	(revision 1196)
@@ -0,0 +1,54 @@
+//#---------------------------------------------------------------------------
+//# python_STFiller.cc: python exposure of c++ STFiller class
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id:$
+//#---------------------------------------------------------------------------
+#include <boost/python.hpp>
+
+#include "ScantableWrapper.h"
+#include "STFillerWrapper.h"
+
+using namespace boost::python;
+
+namespace asap {
+  namespace python {
+
+    void python_STFiller() {
+      class_<STFillerWrapper>("stfiller")
+        .def( init < ScantableWrapper > () )
+        .def( init < std::string, int, int > () )
+        .def("_open", &STFillerWrapper::open)
+        .def("_read", &STFillerWrapper::read)
+        .def("_close", &STFillerWrapper::close)
+        .def("_getdata", &STFillerWrapper::getScantable)
+      ;
+    };
+
+  } //namespace python
+} // namespace asap
+
Index: tags/Release2.1.0b/src/python_STFitEntry.cpp
===================================================================
--- tags/Release2.1.0b/src/python_STFitEntry.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/python_STFitEntry.cpp	(revision 1196)
@@ -0,0 +1,58 @@
+//#---------------------------------------------------------------------------
+//# python_STFitEntry: python exposure of c++ STFitEntry class
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2006
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+#include <boost/python.hpp>
+
+#include "STFitEntry.h"
+
+using namespace boost::python;
+
+namespace asap {
+  namespace python {
+
+    void python_STFitEntry() {
+      class_<STFitEntry>("fitentry")
+        .def( init <> () )
+        .def( init < const STFitEntry& > () )
+        .def("getfixedparameters", &STFitEntry::getParmasks)
+        .def("getparameters", &STFitEntry::getParameters)
+        .def("getfunctions", &STFitEntry::getFunctions)
+	.def("getcomponents", &STFitEntry::getComponents)
+	.def("getframeinfo", &STFitEntry::getFrameinfo)
+        .def("setfixedparameters", &STFitEntry::setParmasks)
+        .def("setparameters", &STFitEntry::setParameters)
+        .def("setfunctions", &STFitEntry::setFunctions)
+	.def("setcomponents", &STFitEntry::setComponents)
+	.def("setframeinfo", &STFitEntry::setFrameinfo)
+      ;
+    };
+
+  } //namespace python
+} // namespace asap
Index: tags/Release2.1.0b/src/python_STLineFinder.cpp
===================================================================
--- tags/Release2.1.0b/src/python_STLineFinder.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/python_STLineFinder.cpp	(revision 1196)
@@ -0,0 +1,51 @@
+//#---------------------------------------------------------------------------
+//# python_STLineFinder.cc: python exposure of C++ STLineFinder class
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+#include <boost/python.hpp>
+
+#include "STLineFinder.h"
+
+using namespace boost::python;
+
+namespace asap {
+  namespace python {
+     void python_STLineFinder() {
+       class_<STLineFinder>("linefinder")
+         .def( init <> () )
+	 .def("setoptions",&STLineFinder::setOptions)
+         .def("setscan",&STLineFinder::setScan)
+         .def("findlines",&STLineFinder::findLines)
+         .def("getmask",&STLineFinder::getMask)
+         .def("getlineranges",&STLineFinder::getLineRanges)
+         .def("getlinerangesinchannels",&STLineFinder::getLineRangesInChannels)
+       ;
+     };
+  } // namespace python
+} // namespace asap
Index: tags/Release2.1.0b/src/python_STMath.cpp
===================================================================
--- tags/Release2.1.0b/src/python_STMath.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/python_STMath.cpp	(revision 1196)
@@ -0,0 +1,75 @@
+//#---------------------------------------------------------------------------
+//# python_STMathWrapper.cc: python exposure of c++ STMath class
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+#include <vector>
+#include <boost/python.hpp>
+
+#include "STMathWrapper.h"
+
+using namespace boost::python;
+
+namespace asap {
+  namespace python {
+    void python_STMath() {
+      class_<STMathWrapper>("stmath")
+        .def( init < > () )
+        .def( init < bool > () )
+        .def("_insitu", &STMathWrapper::insitu)
+        .def("_setinsitu", &STMathWrapper::setInsitu)
+        .def("_average", &STMathWrapper::average)
+        .def("_averagechannel", &STMathWrapper::averageChannel)
+        .def("_averagepol", &STMathWrapper::averagePolarisations)
+        .def("_averagebeams", &STMathWrapper::averageBeams)
+        .def("_unaryop", &STMathWrapper::unaryOperate)
+        .def("_auto_quotient", &STMathWrapper::autoQuotient)
+        .def("_quotient", &STMathWrapper::quotient)
+        .def("_stats", &STMathWrapper::statistic)
+        .def("_freqswitch", &STMathWrapper::freqSwitch)
+        .def("_bin", &STMathWrapper::bin)
+        .def("_resample", &STMathWrapper::resample)
+        .def("_smooth", &STMathWrapper::smooth)
+        .def("_gainel", &STMathWrapper::gainElevation)
+        .def("_convertflux", &STMathWrapper::convertFlux)
+        .def("_convertpol", &STMathWrapper::convertPolarisation)
+        .def("_opacity", &STMathWrapper::opacity)
+        .def("_merge", &STMathWrapper::merge)
+        .def("_rotate_xyphase", &STMathWrapper::rotateXYPhase)
+        .def("_rotate_linpolphase", &STMathWrapper::rotateLinPolPhase)
+        .def("_invert_phase", &STMathWrapper::invertPhase)
+        .def("_swap_linears", &STMathWrapper::swapPolarisations)
+        .def("_freq_align", &STMathWrapper::frequencyAlign)
+        .def("_mx_extract", &STMathWrapper::mxExtract)
+        .def("_lag_flag", &STMathWrapper::lagFlag)
+          ;
+    };
+
+  } //namespace python
+} // namespace asap
+
Index: tags/Release2.1.0b/src/python_STSelector.cpp
===================================================================
--- tags/Release2.1.0b/src/python_STSelector.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/python_STSelector.cpp	(revision 1196)
@@ -0,0 +1,47 @@
+//
+// C++ Implementation: python_STSelector
+//
+// Description: This file is the boost::python wrapper for asap::STSelector
+//
+//
+// Author: Malte Marquarding <asap@atnf.csiro.au>, (C) 2006
+//
+// Copyright: See COPYING file that comes with this distribution
+//
+//
+#include <boost/python.hpp>
+
+#include "STSelector.h"
+
+using namespace boost::python;
+
+namespace asap {
+  namespace python {
+
+    void python_STSelector() {
+      class_<STSelector>("selector")
+        .def( init <> () )
+        .def( init < const STSelector& > () )
+        .def("_getbeams", &STSelector::getBeams)
+        .def("_getifs", &STSelector::getIFs)
+        .def("_getpols", &STSelector::getPols)
+        .def("_getpoltypes", &STSelector::getPolTypes)
+        .def("_getscans", &STSelector::getScans)
+        .def("_getcycles", &STSelector::getCycles)
+        .def("_gettaql", &STSelector::getTaQL)
+        .def("_getorder", &STSelector::getSortOrder)
+        .def("_reset", &STSelector::reset)
+        .def("_setbeams", &STSelector::setBeams)
+        .def("_setifs", &STSelector::setIFs)
+        .def("_setpols", &STSelector::setPolarizations)
+        .def("_setpolstrings", &STSelector::setPolFromStrings)
+        .def("_setscans", &STSelector::setScans)
+        .def("_setcycles", &STSelector::setCycles)
+        .def("_setname", &STSelector::setName)
+        .def("_settaql", &STSelector::setTaQL)
+        .def("_setorder", &STSelector::setSortOrder)
+        .def("_empty", &STSelector::empty)
+      ;
+    };
+  }
+}
Index: tags/Release2.1.0b/src/python_STWriter.cpp
===================================================================
--- tags/Release2.1.0b/src/python_STWriter.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/python_STWriter.cpp	(revision 1196)
@@ -0,0 +1,50 @@
+//#---------------------------------------------------------------------------
+//# python_STWriter.cc: Python binding to C++ SDWriter class.
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+#include <boost/python.hpp>
+
+#include "STWriterWrapper.h"
+
+using namespace boost::python;
+
+namespace asap {
+  namespace python  {
+
+    void python_STWriter() {
+      class_<STWriterWrapper>("stwriter")
+        .def(init <> ())
+        .def(init <std::string> ())
+        .def("setformat", &STWriterWrapper::setFormat,
+        (boost::python::arg("format")="STFITS"))
+        .def("write",     &STWriterWrapper::write);
+      ;
+    };
+  } // namespace python
+} // namespace asap
Index: tags/Release2.1.0b/src/python_Scantable.cpp
===================================================================
--- tags/Release2.1.0b/src/python_Scantable.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/python_Scantable.cpp	(revision 1196)
@@ -0,0 +1,124 @@
+//#---------------------------------------------------------------------------
+//# python_Scantable.cc: python exposure of c++ Scantable class
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+#include <vector>
+
+#include <boost/python.hpp>
+#include <boost/python/args.hpp>
+
+#include "ScantableWrapper.h"
+
+using namespace boost::python;
+
+namespace asap {
+  namespace python {
+
+void python_Scantable() {
+  class_<ScantableWrapper>("Scantable")
+    //.def( init <> () )
+    .def( init < int > () )
+    .def( init < const std::string&, int > () )
+    .def( init < const ScantableWrapper& > () )
+    .def("_copy", &ScantableWrapper::copy)
+    .def("_assign", &ScantableWrapper::assign)
+    .def("getif", &ScantableWrapper::getIF)
+    .def("getifnos", &ScantableWrapper::getIFNos)
+    .def("getbeam", &ScantableWrapper::getBeam)
+    .def("getbeamnos", &ScantableWrapper::getBeamNos)
+    .def("getpol", &ScantableWrapper::getPol)
+    .def("getpolnos", &ScantableWrapper::getPolNos)
+    .def("getscan", &ScantableWrapper::getScan)
+    .def("getscannos", &ScantableWrapper::getScanNos)
+    .def("getcycle", &ScantableWrapper::getCycle)
+    .def("nif", &ScantableWrapper::nif,
+         (boost::python::arg("scanno")=-1) )
+    .def("nbeam", &ScantableWrapper::nbeam,
+         (boost::python::arg("scanno")=-1) )
+    .def("npol", &ScantableWrapper::npol,
+         (boost::python::arg("scanno")=-1) )
+    .def("nchan", &ScantableWrapper::nchan,
+         (boost::python::arg("ifno")=-1) )
+    .def("ncycle", &ScantableWrapper::ncycle,
+         (boost::python::arg("scanno")=-1) )
+    .def("nscan", &ScantableWrapper::nscan)
+    .def("nrow", &ScantableWrapper::nrow)
+    .def("get_fluxunit", &ScantableWrapper::getFluxUnit)
+    .def("set_fluxunit", &ScantableWrapper::setFluxUnit)
+    .def("_setInstrument", &ScantableWrapper::setInstrument)
+    .def("_setfeedtype", &ScantableWrapper::setFeedType)
+    .def("_getspectrum", &ScantableWrapper::getSpectrum,
+         (arg("whichrow")=0, arg("poltype")=std::string("")) )
+    .def("poltype", &ScantableWrapper::getPolType )
+    .def("column_names", &ScantableWrapper::columnNames)
+    .def("_getpollabel", &ScantableWrapper::getPolarizationLabel)
+    .def("_setspectrum",&ScantableWrapper::setSpectrum,
+         (boost::python::arg("whichrow")=0) )
+    .def("_getabcissa", &ScantableWrapper::getAbcissa,
+         (boost::python::arg("whichrow")=0) )
+    .def("_getabcissalabel", &ScantableWrapper::getAbcissaLabel,
+         (boost::python::arg("whichrow")=0) )
+    .def("_getmask", &ScantableWrapper::getMask,
+         (boost::python::arg("whichrow")=0) )
+    .def("_gettsys", &ScantableWrapper::getTsys)
+    .def("_getsourcename", &ScantableWrapper::getSourceName,
+         (boost::python::arg("whichrow")=0) )
+    .def("_getelevation", &ScantableWrapper::getElevation,
+         (boost::python::arg("whichrow")=0) )
+    .def("_getazimuth", &ScantableWrapper::getAzimuth,
+         (boost::python::arg("whichrow")=0) )
+    .def("_getparangle", &ScantableWrapper::getParAngle,
+         (boost::python::arg("whichrow")=0) )
+    .def("_gettime", &ScantableWrapper::getTime,
+         (boost::python::arg("whichrow")=0) )
+    .def("_getdirection", &ScantableWrapper::getDirectionString,
+         (boost::python::arg("whichrow")=0) )
+    .def("_flag", &ScantableWrapper::flag)
+    .def("_save",  &ScantableWrapper::makePersistent)
+    .def("_summary",  &ScantableWrapper::summary,
+         (boost::python::arg("verbose")=true) )
+    .def("_getrestfreqs",  &ScantableWrapper::getRestFrequencies)
+    .def("_setrestfreqs",  &ScantableWrapper::setRestFrequencies)
+    .def("_setcoordinfo", &ScantableWrapper::setCoordInfo)
+    .def("_getcoordinfo", &ScantableWrapper::getCoordInfo)
+    .def("set_dirframe", &ScantableWrapper::setDirection,
+         (boost::python::arg("refstr")="") )
+    .def("_gethistory", &ScantableWrapper::getHistory)
+    .def("_addhistory", &ScantableWrapper::addHistory)
+    .def("_getselection", &ScantableWrapper::getSelection)
+    .def("_setselection", &ScantableWrapper::setSelection)
+    .def("_addfit", &ScantableWrapper::addFit)
+    .def("_getfit", &ScantableWrapper::getFit)
+    .def("_recalcazel", &ScantableWrapper::calculateAZEL)
+    .def("_setsourcetype", &ScantableWrapper::setSourceType)
+  ;
+};
+
+  } // python
+} // asap
Index: tags/Release2.1.0b/src/python_asap.cpp
===================================================================
--- tags/Release2.1.0b/src/python_asap.cpp	(revision 1196)
+++ tags/Release2.1.0b/src/python_asap.cpp	(revision 1196)
@@ -0,0 +1,93 @@
+//#---------------------------------------------------------------------------
+//# python_asap.cc: python module for single dish package asap
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+#include <string>
+#include <vector>
+
+#include <boost/python.hpp>
+#include <boost/python/exception_translator.hpp>
+
+#include <casa/aips.h>
+#include <casa/Exceptions/Error.h>
+#include "ScantableWrapper.h"
+
+
+#include "pyconversions.h"
+#include "python_asap.h"
+
+namespace asap {
+  namespace python {
+void translate_ex(const casa::AipsError& e)
+{
+  // Use the Python 'C' API to set up an exception object
+  PyErr_SetString(PyExc_RuntimeError, e.what());
+}
+
+  }
+}
+using namespace boost::python;
+
+BOOST_PYTHON_MODULE(_asap) {
+  asap::python::python_Scantable();
+  asap::python::python_STFiller();
+  asap::python::python_STSelector();
+  asap::python::python_STMath();
+  asap::python::python_Fitter();
+  asap::python::python_STLineFinder();
+  asap::python::python_STFitEntry();
+  asap::python::python_STWriter();
+  asap::python::python_LineCatalog();
+
+  asap::python::python_Logger();
+  register_exception_translator<casa::AipsError>(&asap::python::translate_ex);
+
+  //std_vector_to_tuple <  > ();
+  from_python_sequence < std::vector< asap::ScantableWrapper >,
+    variable_capacity_policy > ();
+
+  std_vector_to_tuple < int > ();
+  from_python_sequence < std::vector < int >,
+    variable_capacity_policy > ();
+  std_vector_to_tuple < uint > ();
+  from_python_sequence < std::vector < uint >,
+    variable_capacity_policy > ();
+  std_vector_to_tuple < float > ();
+  from_python_sequence < std::vector < float >,
+    variable_capacity_policy > ();
+  std_vector_to_tuple < double > ();
+  from_python_sequence < std::vector < double >,
+    variable_capacity_policy > ();
+  std_vector_to_tuple < std::string > ();
+  from_python_sequence < std::vector < std::string >,
+    variable_capacity_policy > ();
+  std_vector_to_tuple < bool> ();
+  from_python_sequence < std::vector < bool >,
+    variable_capacity_policy > ();
+}
Index: tags/Release2.1.0b/src/python_asap.h
===================================================================
--- tags/Release2.1.0b/src/python_asap.h	(revision 1196)
+++ tags/Release2.1.0b/src/python_asap.h	(revision 1196)
@@ -0,0 +1,53 @@
+//#---------------------------------------------------------------------------
+//# python_asap.h: python module for single dish package asap
+//#---------------------------------------------------------------------------
+//# Copyright (C) 2004
+//# ATNF
+//#
+//# This program is free software; you can redistribute it and/or modify it
+//# under the terms of the GNU General Public License as published by the Free
+//# Software Foundation; either version 2 of the License, or (at your option)
+//# any later version.
+//#
+//# This program is distributed in the hope that it will be useful, but
+//# WITHOUT ANY WARRANTY; without even the implied warranty of
+//# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+//# Public License for more details.
+//#
+//# You should have received a copy of the GNU General Public License along
+//# with this program; if not, write to the Free Software Foundation, Inc.,
+//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+//#
+//# Correspondence concerning this software should be addressed as follows:
+//#        Internet email: Malte.Marquarding@csiro.au
+//#        Postal address: Malte Marquarding,
+//#                        Australia Telescope National Facility,
+//#                        P.O. Box 76,
+//#                        Epping, NSW, 2121,
+//#                        AUSTRALIA
+//#
+//# $Id$
+//#---------------------------------------------------------------------------
+#ifndef PYTHON_ASAP_H
+#define PYTHON_ASAP_H
+
+class casa::AipsError;
+
+namespace asap {
+  namespace python {
+    void translate_ex(const casa::AipsError& e);
+    void python_Scantable();
+    void python_STFiller();
+    void python_STSelector();
+    void python_STMath();
+    void python_Fitter();
+    void python_STLineFinder();
+    void python_STFitEntry();
+    void python_STWriter();
+    void python_LineCatalog();
+    void python_Logger();
+
+  } // python
+} //asap
+
+#endif
Index: tags/Release2.1.0b/test/file-io.py
===================================================================
--- tags/Release2.1.0b/test/file-io.py	(revision 1196)
+++ tags/Release2.1.0b/test/file-io.py	(revision 1196)
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+from asap import *
+
+import os
+outdir = "./output"
+if not os.path.exists(outdir):
+    os.mkdir(outdir)
+
+rcParams['verbose'] = 0
+
+
+print "Test of file i/o"
+
+data = scantable('data/tid-t002.rpf')
+
+data.save('output/test.asap',overwrite=True)
+data.save('output/test.ascii',format='ASCII',overwrite=True)
+data.save('output/test.sdfits',format='SDFITS',overwrite=True)
+
+data2 = scantable('output/test.sdfits')
+data3 = scantable('output/test.asap')
+
+print "File i/o test finished successfully"
Index: tags/Release2.1.0b/test/mopra.py
===================================================================
--- tags/Release2.1.0b/test/mopra.py	(revision 1196)
+++ tags/Release2.1.0b/test/mopra.py	(revision 1196)
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+from asap import *
+rcParams['verbose'] = 0
+print "Test using Mopra dual IF data..."
+print "Reduction test..."
+s = scantable("data/mopra_dualif.rpf")
+q = s.auto_quotient()
+del s
+restfreqs = [110.201,86.243]     # 13CO-1/0, SiO the two IF
+q.set_restfreqs(restfreqs,"GHz") # set the restfrequencies, as not in data
+q.set_unit("km/s")               # set the unit to be used from now on
+q.set_freqframe("LSRK")          # set frequency frame
+q.auto_poly_baseline() # determine and subtract a poly baseline automatically
+q.convert_flux() # K -> Jy
+msk = q.create_mask([-70,20], [20,70]) # line free region - two windows
+rms = q.stats("rms",msk)
+med = q.stats("median",msk)
+
+print "Fitter test..."
+# select IF1
+selection = selector()
+selection.set_ifs(1)
+q.set_selection(selection)
+
+f = fitter()
+f.set_scan(q)
+f.set_function(gauss=2) # fit two gaussians
+f.fit()
+fp = f.get_parameters()
+print "Mopra Test successful"
Index: tags/Release2.1.0b/test/parkes-pol.py
===================================================================
--- tags/Release2.1.0b/test/parkes-pol.py	(revision 1196)
+++ tags/Release2.1.0b/test/parkes-pol.py	(revision 1196)
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+from asap import *
+
+import os
+outdir = "./output"
+if not os.path.exists(outdir):
+    os.mkdir(outdir)
+
+rcParams['verbose'] = 0
+rcParams['plotter.ganged'] = 0
+
+# Don't plot to the screen...
+del plotter
+plotter = asapplotter(False)
+
+print "Test of Parkes polarimetry (P484)"
+
+data_1665 = scantable('data/parkes-pol.rpf')
+data_1665.rotate_linpolphase(-45)
+data_1665.rotate_xyphase(-2)
+data_1665.set_unit('km/s')
+data_1665.set_freqframe('LSRK')
+
+# Look at the first scan
+selection = selector()
+selection.set_scans(0)
+data_1665.set_selection(selection)
+
+d1_5 = data_1665.copy()
+d1_7 = data_1665.copy()
+
+d1_7.set_restfreqs([1667.3590],'MHz')
+
+# Baseline both
+msk = d1_5.create_mask([-30,-25],[-5,0])
+d1_5.poly_baseline(msk,1)
+msk = d1_7.create_mask([-30,-25],[-5,0])
+d1_7.poly_baseline(msk,1)
+
+# merge the two scans back together into a new scantable
+plotscans = merge(d1_5,d1_7)
+print plotscans.summary()
+del d1_5,d1_7,data_1665
+# Plot the results
+plotter.set_mode('p','s')
+plotter.set_layout(2,1)
+plotter.set_range(-30,0)
+selection.reset()
+selection.set_polarisations(['I','Q', 'U', 'V'])
+plotter.set_selection(selection)
+selection.set_polarisations(['I','Plinear'])
+plotter.set_selection(selection)
+selection.set_polarisations(['RR','LL'])
+plotter.set_selection(selection)
+plotter.plot(plotscans)
+plotter.save('output/parkes_rrll.png',dpi=80)
+
+print "Parkes-Pol Test successful"
Index: tags/Release2.1.0b/test/tid.py
===================================================================
--- tags/Release2.1.0b/test/tid.py	(revision 1196)
+++ tags/Release2.1.0b/test/tid.py	(revision 1196)
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+from asap import *
+
+import os
+outdir = "./output"
+if not os.path.exists(outdir):
+    os.mkdir(outdir)
+
+rcParams['verbose'] = 0
+
+rcParams['plotter.ganged'] = 0
+# Don't plot to the screen...
+del plotter
+plotter = asapplotter(False)
+
+print "Test of Tidbinbilla"
+
+# Create the quotient spectra
+data = scantable('data/tid-t002.rpf')
+q = data.auto_quotient()
+# Set the restfreq for each IF
+q.set_restfreqs(freqs= [23694.4700e6,23722.6336e6])
+q.set_unit('km/s')
+q.set_freqframe('LSRK')
+# Align frequencies
+q.freq_align()
+# Recalculate the az/el
+q.recalc_azel()
+# Correct for gain curve and opacity
+q.gain_el()
+q.opacity(0.075)
+# Average in time
+av = q.average_time()
+# Baseline
+msk=av.create_mask([-70,-50],[40,60])
+av.poly_baseline(msk,1)
+plotter.set_mode('i','s')
+plotter.plot(av)
+plotter.save('output/tid.png', dpi=80)
+
+# Do some random processing, just to test these functions
+av.smooth('gauss',5)
+av.scale(1.05)
+av.add(0.05)
+
+print "Tid test finished successfully"
Index: tags/Release2.1.0b/web/commands.html
===================================================================
--- tags/Release2.1.0b/web/commands.html	(revision 1196)
+++ tags/Release2.1.0b/web/commands.html	(revision 1196)
@@ -0,0 +1,220 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<?php include("base.inc"); ?>
+<title>ASAP - ATNF Spectral line Analysis Package</title>
+<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+<meta name="DC.Creator" lang="en" content="personalName=Marquarding,Malte" />
+<meta name="ATNF.Maintainer" content="contactName=Marquarding,Malte" />
+<meta name="DC.Title" lang="en" content="Homepage of Malte Marquarding" />
+<meta name="DC.Title.alt" lang="en" content="" />
+<meta name="DC.Subject" lang="en" content="spectral line" />
+<meta name="DC.Description" lang="en"
+content="Just some links and conatact details" />
+<meta name="DC.Date.created" scheme="ISO8601" content="2004-11-12" />
+<meta name="ATNF.Site" content="Marsfield" />
+<meta name="ATNF.AccessPermission" content="Public" />
+<meta name="ATNF.MetaVersion" content="1.0" />
+<?php standard_head(); ?>
+<STYLE TYPE="text/css">
+<!--
+B.asap {
+ text-align: center;
+ color: blue;
+}
+B.note {
+ color: red;
+}
+PRE.commands {
+ color: blue;
+ font-size: smaller;
+}
+-->
+</STYLE>
+</head>
+<body bgcolor="#FFFFFF" text="#000000">
+<?php
+title_bar("atnf");
+title_crumbs("/computing/software/");
+?>
+<!-- Start Content -->
+<h1><a href="index.html">ASAP</a> 'commands'</h1>
+<pre class="commands">
+ASAP> commands()
+
+    [The scan container]
+        scantable           - a container for integrations/scans
+                              (can open asap/rpfits/sdfits and ms files)
+            copy            - returns a copy of a scan
+            get_scan        - gets a specific scan out of a scantable
+                              (by name or number)
+            set_selection   - set a new subselection of the data
+            get_selection   - get the current selection object
+            summary         - print info about the scantable contents
+            stats           - get specified statistic of the spectra in
+                              the scantable
+            stddev          - get the standard deviation of the spectra
+                              in the scantable
+            get_tsys        - get the TSys
+            get_time        - get the timestamps of the integrations
+            get_sourcename  - get the source names of the scans
+            get_azimuth     - get the azimuth of the scans
+            get_elevation   - get the elevation of the scans
+            get_parangle    - get the parallactic angle of the scans
+            get_unit        - get the current unit
+            set_unit        - set the abcissa unit to be used from this
+                              point on
+            get_abcissa     - get the abcissa values and name for a given
+                              row (time)
+            set_freqframe   - set the frame info for the Spectral Axis
+                              (e.g. 'LSRK')
+            set_doppler     - set the doppler to be used from this point on
+            set_dirframe    - set the frame for the direction on the sky
+            set_instrument  - set the instrument name
+            get_fluxunit    - get the brightness flux unit
+            set_fluxunit    - set the brightness flux unit
+            create_mask     - return an mask in the current unit
+                              for the given region. The specified regions
+                              are NOT masked
+            get_restfreqs   - get the current list of rest frequencies
+            set_restfreqs   - set a list of rest frequencies
+            flag            - flag selected channels in the data
+            save            - save the scantable to disk as either 'ASAP'
+                              or 'SDFITS'
+            nbeam,nif,nchan,npol - the number of beams/IFs/Pols/Chans
+            nscan           - the number of scans in the scantable
+            nrow            - te number of spectra in the scantable
+            history         - print the history of the scantable
+            get_fit         - get a fit which has been stored witnh the data
+            average_time    - return the (weighted) time average of a scan
+                              or a list of scans
+            average_pol     - average the polarisations together.
+                              The dimension won't be reduced and
+                              all polarisations will contain the
+                              averaged spectrum.
+            convert_pol     - convert to a different polarisation type
+            auto_quotient   - return the on/off quotient with
+                              automatic detection of the on/off scans
+                              (matched pairs and 1 off - n on)
+            scale, *, /     - return a scan scaled by a given factor
+            add, +, -       - return a scan with given value added
+            bin             - return a scan with binned channels
+            resample        - return a scan with resampled channels
+            smooth          - return the spectrally smoothed scan
+            poly_baseline   - fit a polynomial baseline to all Beams/IFs/Pols
+            auto_poly_baseline - automatically fit a polynomial baseline
+            recalc_azel     - recalculate azimuth and elevation based on
+                              the pointing
+            gain_el         - apply gain-elevation correction
+            opacity         - apply opacity correction
+            convert_flux    - convert to and from Jy and Kelvin brightness
+                              units
+            freq_align      - align spectra in frequency frame
+            invert_phase    - Invert the phase of the cross-correlation
+            swap_linears    - Swap XX and YY
+            rotate_xyphase  - rotate XY phase of cross correlation
+            rotate_linpolphase - rotate the phase of the complex
+                                 polarization O=Q+iU correlation
+            freq_switch     - perform frequency switching on the data
+            stats           - Determine the specified statistic, e.g. 'min'
+                              'max', 'rms' etc.
+            stddev          - Determine the standard deviation of the current
+                              beam/if/pol
+     [Selection]
+         selector              - a selection object to set a subset of a scantable
+            set_scans          - set (a list of) scans by index
+            set_cycles         - set (a list of) cycles by index
+            set_beams          - set (a list of) beamss by index
+            set_ifs            - set (a list of) ifs by index
+            set_polarisations  - set (a list of) polarisations by name
+                                 or by index
+            set_names          - set a selection by name (wildcards allowed)
+            set_tsys           - set a selection by tsys thresholds
+            reset              - unset all selections
+            +                  - merge to selections
+
+     [Math] Mainly functions which operate on more than one scantable
+
+            average_time    - return the (weighted) time average
+                              of a list of scans
+            quotient        - return the on/off quotient
+            simple_math     - simple mathematical operations on two scantables,
+                              'add', 'sub', 'mul', 'div'
+     [Fitting]
+        fitter
+            auto_fit        - return a scan where the function is
+                              applied to all Beams/IFs/Pols.
+            commit          - return a new scan where the fits have been
+                              commited.
+            fit             - execute the actual fitting process
+            store_fit       - store the fit parameters in the data (scantable)
+            get_chi2        - get the Chi^2
+            set_scan        - set the scantable to be fit
+            set_function    - set the fitting function
+            set_parameters  - set the parameters for the function(s), and
+                              set if they should be held fixed during fitting
+            set_gauss_parameters - same as above but specialised for individual
+                                   gaussian components
+            get_parameters  - get the fitted parameters
+            plot            - plot the resulting fit and/or components and
+                              residual
+    [Plotter]
+        asapplotter         - a plotter for asap, default plotter is
+                              called 'plotter'
+            plot            - plot a scantable
+            save            - save the plot to a file ('png' ,'ps' or 'eps')
+            set_mode        - set the state of the plotter, i.e.
+                              what is to be plotted 'colour stacked'
+                              and what 'panelled'
+            set_selection   - only plot a selected part of the data
+            set_range       - set a 'zoom' window [xmin,xmax,ymin,ymax]
+            set_legend      - specify user labels for the legend indeces
+            set_title       - specify user labels for the panel indeces
+            set_abcissa     - specify a user label for the abcissa
+            set_ordinate    - specify a user label for the ordinate
+            set_layout      - specify the multi-panel layout (rows,cols)
+            set_colors      - specify a set of colours to use
+            set_linestyles  - specify a set of linestyles to use if only
+                              using one color
+            set_histrogra,  - plot in historam style
+            set_mask        - set a plotting mask for a specific polarization
+
+    [Reading files]
+        reader              - access rpfits/sdfits files
+            open            - attach reader to a file
+            close           - detach reader from file
+            read            - read in integrations
+            summary         - list info about all integrations
+
+    [General]
+        commands            - this command
+        print               - print details about a variable
+        list_scans          - list all scantables created bt the user
+        list_files          - list all files readable by asap (default rpf)
+        del                 - delete the given variable from memory
+        range               - create a list of values, e.g.
+                              range(3) = [0,1,2], range(2,5) = [2,3,4]
+        help                - print help for one of the listed functions
+        execfile            - execute an asap script, e.g. execfile('myscript')
+        list_rcparameters   - print out a list of possible values to be
+                              put into $HOME/.asaprc
+        mask_and,mask_or,
+        mask_not            - boolean operations on masks created with
+                              scantable.create_mask
+
+    Note:
+        How to use this with help:
+                                         # function 'summary'
+        [xxx] is just a category
+        Every 'sub-level' in this list should be replaces by a '.' Period when
+        using help
+        Example:
+            ASAP> help scantable # to get info on ths scantable
+            ASAP> help scantable.summary # to get help on the scantable's
+            ASAP> help average_time
+
+</pre>
+<!-- End Content -->
+<?php footer(); ?>
+</body>
+</html>
Index: tags/Release2.1.0b/web/index.html
===================================================================
--- tags/Release2.1.0b/web/index.html	(revision 1196)
+++ tags/Release2.1.0b/web/index.html	(revision 1196)
@@ -0,0 +1,140 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<?php include("base.inc"); ?>
+<title>ASAP - ATNF Spectral line Analysis Package</title>
+<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+<meta name="DC.Creator" lang="en" content="personalName=Marquarding,Malte" />
+<meta name="ATNF.Maintainer" content="contactName=Marquarding,Malte" />
+<meta name="DC.Title" lang="en" content="Homepage of Malte Marquarding" />
+<meta name="DC.Title.alt" lang="en" content="" />
+<meta name="DC.Subject" lang="en" content="spectral line" />
+<meta name="DC.Description" lang="en"
+content="Just some links and conatact details" />
+<meta name="DC.Date.created" scheme="ISO8601" content="2004-11-12" />
+<meta name="ATNF.Site" content="Marsfield" />
+<meta name="ATNF.AccessPermission" content="Public" />
+<meta name="ATNF.MetaVersion" content="1.0" />
+<?php standard_head(); ?>
+<STYLE TYPE="text/css">
+<!--
+B.asap {
+ text-align: center;
+ color: blue;
+}
+B.note {
+ color: red;
+}
+-->
+</STYLE>
+</head>
+<body bgcolor="#FFFFFF" text="#000000">
+<?php
+title_bar("atnf");
+?>
+<!-- Start Content -->
+<h1>ASAP Homepage</h1>
+<div align="center">
+<img src="images/asap.png">
+</div>
+<h2>Introduction</h2>
+The <b class='asap'>A</b>TNF <b class='asap'>S</b>pectral <b
+class='asap'>A</b>nalysis <b class='asap'>P</b>ackage is a new
+software package to reduce <em>single-dish, single-pointing spectral
+line observations</em>. At this stage it is tuned towards data from
+ATNF instruments and reads/writes <em>rpfits, sdfits and ms</em> data.
+<a href="intro.html">more...<a>
+
+<h2>Current Release</h2>
+ASAP latest stable version 2.0 was released on April, 27<sup>th</sup> 2006. It can be obtained in the <a href="#download">Download</a> section.
+
+<h2>Screenshots</h2>
+<a href="screenshots.html">Here</a> are some example plots.
+
+<h2>How to run</h2>
+To get started, simply type "asap" at the *nix command line prompt.
+<pre style="color: blue">
+Loading ASAP...
+Welcome to ASAP v2.0 (2006/04/27) - the ATNF Spectral Analysis Package
+
+Please report any bugs via:
+http://sourcecode.atnf.csiro.au/cgi-bin/trac_asap.cgi/newticket
+
+[IMPORTANT: ASAP is 0-based]
+Type commands() to get a list of all available ASAP commands.
+
+ASAP>
+</pre>
+
+<h2>Defects and enhancement requests</h2>
+Bugs and enhancement requests can be submitted online, via the <a href="http://sourcecode.atnf.csiro.au/cgi-bin/trac_asap.cgi/newticket">
+ASAP project tracking page</a>. Please also check if the defect has already been sumbitted under
+<a href="http://sourcecode.atnf.csiro.au/cgi-bin/trac_asap.cgi/report/1">current tickets</a>.
+
+
+<h2>Documentation</h2>
+ASAP has a user guide for general use and telescope specific examples,
+and a reference manual for people who want to write scripts to reduce
+and analyse their data.
+<ul>
+  <li> List of commands <a href="commands.html">(html)</a> </li>
+  <li> User Guide <a href="userguide/">(html)</a> <a href="userguide.pdf">(pdf)</a></li>
+  <li> Reference manual <a href="refman/">(html)</a> <a href="refman.pdf">(pdf)</a></li>
+  <li><a href="tutorials/">Tutorials</a></li>
+</ul>
+<p>
+More detailed help is available via the <b>help</b> function.
+<p>
+The <a href="http://sourcecode.atnf.csiro.au/cgi-bin/trac_asap.cgi">Trac page</a>
+for ASAP also contains information about building from source and code
+revisions and milestones. The current
+
+
+<h2>ASAP Mailinglist </h2>
+
+There is an asap mailing list asap-users@atnf.csiro.au for users of
+asap. This list will be used to announce updates to the code and allow
+general user discussion (please send bug and feature requests to
+asap@atnf.csiro.au). Messages sent to this list are archived at
+<a href="http://www.atnf.csiro.au/lists/asap-users"> http://www.atnf.csiro.au/lists/asap-users</a>
+
+
+<p> You need to be suscribed to send and receive messages from this
+list. To subscribe, please send a message to <pre>
+asap-users-request@atnf.csiro.au </pre> with the single word
+"subscribe" in the body of the message. Send this from your normal
+email account. If you wish to be removed from this list send an email
+to asap-users-request@atnf.csiro.au with the single word
+"unsubscribe" in the body of the message.
+
+
+<a name="download"><h2>Download</h2></a>
+ASAP source code and binaries are available in the <a href="http://www.atnf.csiro.au/pub/software/asap/">ATNF ftp area</a>
+<ul>
+  <li> A binary tar archive of the latest stable release <a
+      href=ftp://ftp.atnf.csiro.au/pub/software/asap/asap_linux.tar.bz2>linux binary</a>.
+      You will need to have the following libraries:<br>
+      gcc-3.3, blas, lapack, g2c, boost_python and of course
+      python2.3.<br>
+      The matplotlib python module has to be installed too.
+  </li>
+  <li>For Mac OSX 10.4 follow <a href="./osx.html">these instructions.</a></li>
+  <li>Current stable release source code snapshot <a
+      href="ftp://ftp.atnf.csiro.au/pub/software/asap/asap_src.tar.bz2">asap source</a>
+      You will need a full aips++ installation, or cyou an try and use the
+      <a href="http://www.atnf.csiro.au/pub/software/asap/casa_asap.tar.bz2">mini-aips++ tree</a>,
+       necessary to build ASAP.</li>
+  <li>The ASAP data package, which contains information about observatories and
+      coordinate frames can be downloaded <a href="http://www.atnf.csiro.au/pub/software/asap/asap_data.tar.bz2">here</a>.
+      You need this if you are building ASAP from source.</li>
+</ul>
+<p>
+
+<h2>Important</h2>
+ASAP is 0-based, i.e. all ranges (vectors) go from
+0..n-1, if n is the number of elements.
+<br>
+<!-- End Content -->
+<?php footer(); ?>
+</body>
+</html>
Index: tags/Release2.1.0b/web/intro.html
===================================================================
--- tags/Release2.1.0b/web/intro.html	(revision 1196)
+++ tags/Release2.1.0b/web/intro.html	(revision 1196)
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<?php include("base.inc"); ?>
+<title>ASAP - ATNF Spectral line Analysis Package</title>
+<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+<meta name="DC.Creator" lang="en" content="personalName=Marquarding,Malte" />
+<meta name="ATNF.Maintainer" content="contactName=Marquarding,Malte" />
+<meta name="DC.Title" lang="en" content="Homepage of Malte Marquarding" />
+<meta name="DC.Title.alt" lang="en" content="" />
+<meta name="DC.Subject" lang="en" content="spectral line" />
+<meta name="DC.Description" lang="en"
+content="Just some links and conatact details" />
+<meta name="DC.Date.created" scheme="ISO8601" content="2004-11-12" />
+<meta name="ATNF.Site" content="Marsfield" />
+<meta name="ATNF.AccessPermission" content="Public" />
+<meta name="ATNF.MetaVersion" content="1.0" />
+<?php standard_head(); ?>
+<STYLE TYPE="text/css">
+<!--
+B.asap {
+ text-align: center;
+ color: blue;
+}
+B.note {
+ color: red;
+}
+-->
+</STYLE>
+</head>
+<body bgcolor="#FFFFFF" text="#000000">
+<?php
+title_bar("atnf");
+title_crumbs("/computing/software/");
+?>
+<!-- Start Content -->
+<h1>ASAP background</h1>
+ASAP is developed in c++ using a thin <a
+href="http://www.python.org">python</a> front-end, to make it
+scriptable. The ASAP user interface is <a
+href="http://ipython.scipy.org">ipython</a>. The c++ code reuses
+several <a href="http://aips2.nrao.edu/daily/docs/casa.html">casa</a>
+libraries.
+It has been developed in the last nine months and has been released to
+ATNF users.
+ASAP v2.0 underwent a major re-design to support varying numbers of channels per IF (or Beam). It also allows much more flexible data selection via the <b>seletcor</b> object.
+Currently, the package has only been tested on linux (debian) system,
+other linux and OSX binaries are going to be avaliable soon.
+<br>
+<!-- End Content -->
+<?php footer(); ?>
+</body>
+</html>
Index: tags/Release2.1.0b/web/osx.html
===================================================================
--- tags/Release2.1.0b/web/osx.html	(revision 1196)
+++ tags/Release2.1.0b/web/osx.html	(revision 1196)
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<?php include("base.inc"); ?>
+<title>ASAP - ATNF Spectral line Analysis Package</title>
+<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+<meta name="DC.Creator" lang="en" content="personalName=Marquarding,Malte" />
+<meta name="ATNF.Maintainer" content="contactName=Marquarding,Malte" />
+<meta name="DC.Title" lang="en" content="Homepage of Malte Marquarding" />
+<meta name="DC.Title.alt" lang="en" content="" />
+<meta name="DC.Subject" lang="en" content="spectral line" />
+<meta name="DC.Description" lang="en" content="Just some links and contact details" />
+<meta name="DC.Date.created" scheme="ISO8601" content="2005-08-02" />
+<meta name="ATNF.Site" content="Marsfield" />
+<meta name="ATNF.AccessPermission" content="Public" />
+<meta name="ATNF.MetaVersion" content="1.0" />
+<?php standard_head(); ?>
+<STYLE TYPE="text/css">
+<!--
+B.asap {
+ text-align: center;
+ color: blue;
+}
+B.note {
+ color: red;
+}
+-->
+</STYLE>
+</head>
+<body bgcolor="#FFFFFF" text="#000000">
+<?php
+title_bar("atnf");
+title_crumbs("/computing/software/");
+?>
+<!-- Start Content -->
+<h1>ASAP OS X (10.4) install</h1>
+This might or might not work with 10.3.
+<h2>Getting stuff</h2>
+<a href="http://pythonmac.org/packages">pythonmac.org</a> has
+a pre-packaged python and some  modules.
+<br>
+From the section <b>Packages for Universal...</b>
+<br>get and install
+<ul>
+  <li>
+<a
+      href="http://pythonmac.org/packages/py24-fat/dmg/Universal-MacPython-2.4.3-2006-04-07.dmg">Python</a> </li>
+  <li>
+<a
+      href="http://pythonmac.org/packages/py24-fat/mpkg/matplotlib-0.87.2-py2.4-macosx10.4.mpkg.zip">matplotlib</a> </li>
+</ul>
+You also need to get numpy:
+<ul>
+  <li>
+<a
+      href="http://prdownloads.sourceforge.net/numpy/numpy-0.9.6-py2.4-macosx10.4.dmg?download">numpy</a> </li>
+</ul>
+<br>
+Next get <a href="http://ipython.scipy.org/dist/ipython-0.7.2.tar.gz">ipython</a> and run:<br>
+<pre>
+sudo python setup.py install
+</pre>
+from within the ipython-0.7.2 directory.
+<br>
+Then
+<pre>
+sudo ln -s /Library/Frameworks/Python.framework/Versions/2.4/bin/ipython /usr/bin/ipython
+</pre>
+<h2>Tweaking it</h2>
+Run the following in a terminal:<br>
+As 'root' or 'sudo' edit<br>
+<b>/System/Library/Frameworks/Python.framework/Versions/2.4/lib/python2.4/site-packages/matplotlib/mpl-data/matplotlibrc</b><br>
+In this file replace<br>
+<b> backend    : WXAgg</b><br>
+with<br>
+<b> backend    : TkAgg</b><br>
+
+<h2>Oh yeah there is ASAP, too</h2>
+Get
+<a
+href="ftp://ftp.atnf.csiro.au/pub/software/asap/asap_osx.tar.bz2">this</a>.
+Unpack and run.<br>
+<pre>
+tar jxvf asap_osx.tar.bz2
+sudo sh install.sh
+</pre>
+
+<h2>Run ASAP</h2>
+Now you should be able to run "asap". On first run you might have to
+answer a few set-up questions (ipython) and get a lot of font related
+errors (matplotlib). Ignore these.
+
+<!-- End Content -->
+<?php footer(); ?>
+</body>
+</html>
Index: tags/Release2.1.0b/web/screenshots.html
===================================================================
--- tags/Release2.1.0b/web/screenshots.html	(revision 1196)
+++ tags/Release2.1.0b/web/screenshots.html	(revision 1196)
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<?php include("base.inc"); ?>
+<title>ASAP - ATNF Spectral line Analysis Package</title>
+<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+<meta name="DC.Creator" lang="en" content="personalName=Marquarding,Malte" />
+<meta name="ATNF.Maintainer" content="contactName=Marquarding,Malte" />
+<meta name="DC.Title" lang="en" content="Homepage of Malte Marquarding" />
+<meta name="DC.Title.alt" lang="en" content="" />
+<meta name="DC.Subject" lang="en" content="spectral line" />
+<meta name="DC.Description" lang="en"
+content="Just some links and conatact details" />
+<meta name="DC.Date.created" scheme="ISO8601" content="2004-11-12" />
+<meta name="ATNF.Site" content="Marsfield" />
+<meta name="ATNF.AccessPermission" content="Public" />
+<meta name="ATNF.MetaVersion" content="1.0" />
+<?php standard_head(); ?>
+<STYLE TYPE="text/css">
+<!--
+B.asap {
+ text-align: center;
+ color: blue;
+}
+-->
+</STYLE>
+</head>
+<body bgcolor="#FFFFFF" text="#000000">
+<?php
+title_bar("atnf");
+title_crumbs("/computing/software/");
+?>
+<!-- Start Content -->
+<h1>ASAP Screenshots</h1>
+<p>&nbsp;</p>
+<div align="center">
+<img src="images/screenshot.png"><br>
+<img src="images/orion.png"><br>
+<img src="images/ngc253.png"><br>
+<img src="images/ngc253fit.png"><br>
+</div>
+
+<br>
+<!-- End Content -->
+<?php footer(); ?>
+</body>
+</html>
Index: tags/Release2.1.0b/web/tutorials/index.html
===================================================================
--- tags/Release2.1.0b/web/tutorials/index.html	(revision 1196)
+++ tags/Release2.1.0b/web/tutorials/index.html	(revision 1196)
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<?php include("base.inc"); ?>
+<title>ASAP - Tutorials</title>
+<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+<meta name="DC.Creator" lang="en" content="personalName=Marquarding,Malte" />
+<meta name="ATNF.Maintainer" content="contactName=Marquarding,Malte" />
+<meta name="DC.Title" lang="en" content="ASAP tutorials" />
+<meta name="DC.Title.alt" lang="en" content="" />
+<meta name="DC.Subject" lang="en" content="spectral line" />
+<meta name="DC.Description" lang="en"
+content="Just some links and conatact details" />
+<meta name="DC.Date.created" scheme="ISO8601" content="2005-08-18" />
+<meta name="ATNF.Site" content="Marsfield" />
+<meta name="ATNF.AccessPermission" content="Public" />
+<meta name="ATNF.MetaVersion" content="1.0" />
+<?php standard_head(); ?>
+<STYLE TYPE="text/css">
+<!--
+B.asap {
+ text-align: center;
+ color: blue;
+}
+B.note {
+ color: red;
+}
+-->
+</STYLE>
+</head>
+<body bgcolor="#FFFFFF" text="#000000">
+<?php
+title_bar("atnf");
+title_crumbs("/computing/software/");
+?>
+<!-- Start Content -->
+<h1>Tutorials</h1>
+<ul>
+  <li>A tutorial written for the Mopra 2005 Workshop  <a
+      href="tutorial.html">(html)</a>, and the data used in the tutorial<a
+      href="../data/2005-05-08_0350.rpf.gz"> (rpf.gz)</a>
+  </li>
+  <li>A tutorial on how to write "batch" scripts <a href="tutorial2.html">(html)</a></li>
+</ul>
+<br>
+<!-- End Content -->
+<?php footer(); ?>
+</body>
+</html>
Index: tags/Release2.1.0b/web/tutorials/tutorial.html
===================================================================
--- tags/Release2.1.0b/web/tutorials/tutorial.html	(revision 1196)
+++ tags/Release2.1.0b/web/tutorials/tutorial.html	(revision 1196)
@@ -0,0 +1,148 @@
+<html>
+<head>
+<title>Mopra 2005 Workshop - Data reduction tutorial worksheet</title>
+<style type="text/css" media="all">
+div.shell {
+background-color: #dbfff9;
+padding: 0.1em;
+}
+div.code {
+background-color: #c9ffbf;
+padding: 0.1em;
+}
+div.main {
+margin: 0.2em 0.2em 0.2em 0.2em;
+text-align: left;
+font-family: sans-serif;
+width: auto;
+}
+h1.custom {
+background-color: #aaaaaa;
+color: #000000;
+padding: 0.5em;
+font-family: sans-serif;
+font-size: x-large;
+}
+</style>
+</head>
+<body>
+<div>
+<h1 class=custom>
+Introduction
+</h1>
+<div class=main align=center>
+This is a short tutorial on how to use ASAP for simple on of dual IF
+observations. It is highly customisable via ".aipsrc" parameters. It
+can read rpfits,sdfits and measurement sets, and export to sdfits, ms,
+ASCII and image fits.  ASAP provides a lot more functionality, such as
+frequency alignment, averaging which can't be presented here.
+</div>
+<h1 class=custom>
+Part I - Reduction
+</h1>
+<div class=main align=center>
+To start ASAP type the follwing at  the *nix command line prompt
+<div class=shell>
+<pre>
+localhost> asap
+</pre>
+</div>
+To get the list of available commands in asap type:
+<div class=code><pre>
+commands()
+</pre></div>
+Help can be accessed by using <em>help</em>:
+<div class=code><pre>
+help(scantable)
+help(scantable.summary)
+</pre></div>
+To start we are reading in the data into a scantable, which can be accessed via th variable <em>s</em>. After reading in we have a look at the data.
+<div class=code><pre>
+s = scantable("2005-05-08_0350.rpf")
+s.summary()
+</pre></div>
+We can plot the scan now. First we set up the plotter to plot "IF" as stcked colours and "time" across panels. Then we issue the plot command.
+<div class=code><pre>
+plotter.set_mode("IF","time")
+plotter.plot(s) # plot s
+</pre></div>
+Now we can build the quotient. This applies the quotient to both IFs.
+<div class=code><pre>
+q = s.auto_quotient()
+plotter.plot(q) # plot q
+</pre></div>
+Now we can set some information anbout the velocity setup. We set the rest frequencies for 13Co and SiO and want to operate in "LSRK".
+<div class=code><pre>
+restfreqs = [110.201,86.243]     # 13CO-1/0, SiO the two IF
+q.set_restfreqs(restfreqs,"GHz") # set the restfrequencies, as not in data
+q.set_unit("km/s")               # set the unit to be used from now on
+q.set_freqframe("LSRK")          # set frequency frame
+plotter.plot()                   # replot, should show velocity now
+</pre></div>
+Now we can subtract a baseline. ASAP can do this automatically if ther is good S/N and the lines aren't too broad.
+<div class=code><pre>
+q.auto_poly_baseline() # determine and subtract a poly baseline automatically
+plotter.plot() # replot
+</pre></div>
+We can zoom in on the spectrum and format the title and legend. ASAP accepts LATEX math expressions.
+<div class=code><pre>
+plotter.set_range(-35,35)                                  # zoom in  bit
+plotter.set_legend([r"$^{13}CO(1\leftarrow 0)$",r"$SiO$"]) # make nice latex IF labels
+plotter.set_title(['Mopra Tutorial 2005'])                 # set the title
+</pre></div>
+Now convert Brightness temperature to Flux density.
+<div class=code><pre>
+q.convert_flux() # K -> Jy
+plotter.plot()
+</pre></div>
+This "final" plot can be saved now as "png", "ps" or "eps"
+<div class=code><pre>
+plotter.save("tutorial.png")
+plotter.save("tutorial.eps")
+</pre></div>
+We can also do some stats on the spectra. We select a line free region first, which get applied to the statistics.
+<div class=code><pre>
+msk = q.create_mask([-70,20], [20,70]) # line free region - two windows
+rms = q.stats("rms",msk)
+med = q.stats("median",msk)
+</pre></div>
+</div>
+<h1 class=custom>
+Part II - Fitting
+</h1>
+<div class=main align=center>
+This part shows how easy it is to fit gaussian line profiles.
+<div class=code><pre>
+f = fitter()
+</pre></div>
+We start with the first spectrum (IF=0) bys setting the selection. We want to fit two gaussian components.
+<div class=code><pre>
+sel = selector()
+sel.set_ifs(0)
+q.set_selection(sel)
+f.set_scan(q)
+f.set_function(gauss=2) # fit two gaussians
+f.fit()
+f.plot(
+</pre></div>
+The second IFs spectrum is more complex. We select it and fit seven gaussians to it. Here we also plot residuals, the individual components and the fit parameters.
+<div class=code><pre>
+sel.set_ifs(1)
+q.set_selection(sel)
+f.set_function(gauss=7)
+f.fit()
+f.plot(residual=True)
+f.plot(components=[0,1,2,3,4,5,6,-1])
+f.plot(components=[0,1,2,3,4,5,6,-1],plotparms=True)
+</pre></div>
+</div>
+<h1 class=custom>
+Appendix
+</h1>
+<div class=main align=center>
+More info can be found on the ASAP homepage
+<a href="http://www.atnf.csiro.au/computing/software/asap">
+http://www.atnf.csiro.au/computing/software/asap</a>
+</div>
+</body>
+</html>
Index: tags/Release2.1.0b/web/tutorials/tutorial2.html
===================================================================
--- tags/Release2.1.0b/web/tutorials/tutorial2.html	(revision 1196)
+++ tags/Release2.1.0b/web/tutorials/tutorial2.html	(revision 1196)
@@ -0,0 +1,113 @@
+<html>
+<head>
+<title>Batch processing tutorial</title>
+<style type="text/css" media="all">
+div.shell {
+background-color: #dbfff9;
+padding: 0.1em;
+}
+div.code {
+background-color: #c9ffbf;
+padding: 0.1em;
+}
+div.main {
+margin: 0.2em 0.2em 0.2em 0.2em;
+text-align: left;
+font-family: sans-serif;
+width: auto;
+}
+h1.custom {
+background-color: #aaaaaa;
+color: #000000;
+padding: 0.5em;
+font-family: sans-serif;
+font-size: x-large;
+}
+</style>
+</head>
+<body>
+<div>
+<h1 class=custom>
+Introduction
+</h1>
+<div class=main align=center>
+This tutorial is aimed at observers who want to "batch reduce" their
+data, i.e. process multiple files the same way.
+</div>
+<h1 class=custom>
+Part I - Need to knows
+</h1>
+<div class=main align=center>
+As ASAP is python based, we need to know a few commands and expression.
+Python has "lists", which are basically vectors. We will use
+these extensively.<br>
+<div class=code><pre>
+x = range(5) # creates a vector of length 5, x = [0,1,2,3,4]
+len(x)       # gives the length of the vector
+x[0]         # gets the first element of the vector
+x[-1]        # gets the last element of the vector
+</pre></div>
+"for" loops are very useful. The code which should be run in the loop
+has to be indented. The end of the loop is indicated by the fact that
+the next command is aligned with the for.<br>
+The following code prints out the numbers [0..4]
+<div class=code><pre>
+for i in range(5):
+   print i
+</pre></div>
+Vectors can also be filled:
+<div class=code><pre>
+x = [] # empty
+for i in range(5):
+   x.append(i*5)
+</pre></div>
+The output is x = [0,5,10,15,20]<br>
+Now we can access those elements like we did above (by index), or directly:
+<div class=code><pre>
+for i in x:
+   print i
+</pre></div>
+That's all we need to know!
+
+</div>
+<h1 class=custom>
+Part II - the code
+</h1>
+<div class=main align=center>
+This shows example code to process multiple files.<p>
+Let's say we have three files (a.rpf, b.rpf, c.rpf)  which contain
+on/off pairs and we want to get the quotients<br>
+<div class=code><pre>
+fnames = ["a.rpf","b.rpf","c.rpf"] # First we set up a vector of filenames
+vec = []                           # a vector to hold the scantables
+for f in fnames:
+   vec.append(scantable(f))        # fill the vector with scantables
+</pre></div>
+Then we can loop over all scantables:
+<div class=code><pre>
+quotients = []                        # to hold the quotient scantables
+for scan in vec:                      # loop over input scantables
+   quotients.append(scan.auto_quotient()) # add this quotient to the vector
+</pre></div>
+Let's have a look at the vector, and subtract a baseline
+<div class=code><pre>
+for q in quotients:
+   print q
+   q.auto_poly_baseline(order=1,insitu=True)
+</pre></div>
+Now we average the lot together:
+<div class=code><pre>
+av = average_time(quotients)
+</pre></div>
+
+</div>
+<h1 class=custom>
+Appendix
+</h1>
+<div class=main align=center>
+More info can be found on the ASAP homepage
+<a href="http://www.atnf.csiro.au/computing/software/asap">
+http://www.atnf.csiro.au/computing/software/asap</a>
+</div>
+</body>
+</html>
