Index: tags/asap1/COPYING
===================================================================
--- tags/asap1/COPYING	(revision 819)
+++ tags/asap1/COPYING	(revision 819)
@@ -0,0 +1,23 @@
+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: Australia Telescope National Facility,
+                       P.O. Box 76,
+                       Epping, NSW, 2121,
+                       AUSTRALIA
Index: tags/asap1/Makefile
===================================================================
--- tags/asap1/Makefile	(revision 819)
+++ tags/asap1/Makefile	(revision 819)
@@ -0,0 +1,61 @@
+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 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/asap1/admin/generate_data.sh
===================================================================
--- tags/asap1/admin/generate_data.sh	(revision 819)
+++ tags/asap1/admin/generate_data.sh	(revision 819)
@@ -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/asap1/admin/log2req.pl
===================================================================
--- tags/asap1/admin/log2req.pl	(revision 819)
+++ tags/asap1/admin/log2req.pl	(revision 819)
@@ -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/asap1/admin/newreq.sh
===================================================================
--- tags/asap1/admin/newreq.sh	(revision 819)
+++ tags/asap1/admin/newreq.sh	(revision 819)
@@ -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/asap1/admin/reqtable.tex
===================================================================
--- tags/asap1/admin/reqtable.tex	(revision 819)
+++ tags/asap1/admin/reqtable.tex	(revision 819)
@@ -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/asap1/admin/requirements1.tex
===================================================================
--- tags/asap1/admin/requirements1.tex	(revision 819)
+++ tags/asap1/admin/requirements1.tex	(revision 819)
@@ -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/asap1/admin/requirements2.tex
===================================================================
--- tags/asap1/admin/requirements2.tex	(revision 819)
+++ tags/asap1/admin/requirements2.tex	(revision 819)
@@ -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/asap1/admin/smartref.sty
===================================================================
--- tags/asap1/admin/smartref.sty	(revision 819)
+++ tags/asap1/admin/smartref.sty	(revision 819)
@@ -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/asap1/admin/sourcerelease.sh
===================================================================
--- tags/asap1/admin/sourcerelease.sh	(revision 819)
+++ tags/asap1/admin/sourcerelease.sh	(revision 819)
@@ -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/asap1/bin/asap
===================================================================
--- tags/asap1/bin/asap	(revision 819)
+++ tags/asap1/bin/asap	(revision 819)
@@ -0,0 +1,84 @@
+#!/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..."
+
+prefix='/usr/local'
+
+# check if user set
+if [ "x${ASAPDATA}" == "x" ]; then
+    ASAPDATA="${prefix}/share/asap"
+    export ASAPDATA
+fi
+
+if [ ! -d "${ASAPDATA}" ]; then
+    echo "Couldn't locate the asap data directory"
+    echo "Please set ASAPDATA"
+    #ASAPDATA="/nfs/aips++"
+    #export ASAPDATA
+    #exit 1
+fi
+
+# Keep casa measures happy
+if test -d "${ASAPDATA}/data" ; then
+    platform='linux_gnu'
+    if [ `uname -s` == 'Darwin' ]; then
+	platform='darwin'
+    fi
+    AIPSPATH="${ASAPDATA} ${platform} somewhere localhost"
+    export AIPSPATH
+else
+    echo "Couldn't find the ASAP data directory"
+    exit 1
+fi
+
+# started for the first time
+ASAPUSERDIR="${HOME}/.asap"; export ASAPUSERDIR
+if [ ! -d ${ASAPUSERDIR} ]; then
+    echo 'First time ASAP use. Setting up ~/.asap'
+    mkdir "${ASAPUSERDIR}"
+    cp "${ASAPDATA}/ipythonrc-asap" "${ASAPUSERDIR}/"
+    echo "#Dummy placeholder. All info is in ipythonrc-asap" > "${ASAPUSERDIR}/ipythonrc"
+    touch "${ASAPUSERDIR}/asapuserfuncs.py"
+fi
+
+ip="`which ipython`"
+
+if [ ! -x "$ip" ]; then
+    echo "Can't find, or no execute permissions for 'ipython'"
+    echo "Running asap through 'python':"
+    p="`which python`"
+    $p -c 'from asap import *'
+else
+    $ip -ipythondir "${ASAPUSERDIR}" -p 'asap' $*
+fi
Index: tags/asap1/bin/install.sh
===================================================================
--- tags/asap1/bin/install.sh	(revision 819)
+++ tags/asap1/bin/install.sh	(revision 819)
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+prefix=/usr/local
+pypath=${prefix}/lib/python2.3
+base=`pwd`
+
+if [ ! -d ${pypath} ]; then
+   echo "python2.3 not installed"
+   exit 1
+else
+   if [ -d "${pypath}/site-packages/asap" ]; then
+      echo "removing old asap module"
+      rm -f ${pypath}/site-packages/asap/*
+   else
+      mkdir "${pypath}/site-packages/asap"
+   fi
+   echo "installing asap python module"
+   cp build/* ${pypath}/site-packages/asap
+   echo "installing asap startup script"
+   cp bin/asap /usr/local/bin
+fi
+
+if [ ! -d ${prefix}/share/asap ]; then
+    echo "creating asap data directory"
+    mkdir ${prefix}/share/asap
+fi
+
+cp share/ipythonrc-asap /usr/local/share/asap/
+
+if [ -d /usr/local/share/asap/data ]; then
+    echo "removing old data directory"
+   rm -rf /usr/local/share/asap/data
+fi
+
+echo "extracting and installing asap data directory"
+cd /usr/local/share/asap
+tar jxf ${base}/share/data.tar.bz2 
+echo "The asap python module has been installed in"
+echo " ${pypath}/site-packages"
+echo "asap data is in ${prefix}/share/asap"
Index: tags/asap1/bugs/README
===================================================================
--- tags/asap1/bugs/README	(revision 819)
+++ tags/asap1/bugs/README	(revision 819)
@@ -0,0 +1,20 @@
+This directory contains ASAP defects
+====================================
+
+Each defect gets a defect number assigned and a file is created in the
+directory containing the traffic on the defect.
+
+The bug number is solely determined by the last entry in this directory.
+
+* Defects/bugs are sent to asap@atnf.csiro.au
+* assign bug id  asap0000, asap0001 ...
+* add bugs/asapNNNN.txt for new defect
+
+* Bug fixes have to be cvs tagged with the bug id
+  - cd <root asap dir>
+  - cvs tag asapNNNN
+* trunk has to be merged with the fixes branch
+  - cd <other root dir>
+  - cvs update -d -j Release-1 -j asapNNNN
+
+MM
Index: tags/asap1/bugs/asap0000.txt
===================================================================
--- tags/asap1/bugs/asap0000.txt	(revision 819)
+++ tags/asap1/bugs/asap0000.txt	(revision 819)
@@ -0,0 +1,145 @@
+asap0000
+========
+Hi
+
+Jim Lovell has discovered a strange corruption bug in asap.
+
+When we run:
+
+ scans = scantable('2005-03-09_172811_t002.rpf')
+ scans.summary(verbose=True)
+
+repeatably the source position become corrupted (generally it seems to be
+after the third "scantable" construction.
+
+The above data is in /DATA/VULCAN_1/cphillip/vulcan/data/asap, but I think
+any data will do.
+
+Cheers
+Chris
+
+
+---------------------------------------------------------
+**OK
+
+ASAP> scans.summary(verbose=True)
+
+--------------------------------------------------------------------------------
+ Scan Table Summary
+--------------------------------------------------------------------------------
+Beams:         1
+IFs:           2
+Polarisations: 1
+Channels:      4096
+
+Observer:      Auto_Spec
+Obs Date:      2005/03/09/17:29:22
+Project:       NGC6334Fw
+Obs. Type:
+Antenna Name:  DSS-43
+Flux Unit:     K
+Rest Freqs:    [2.39639e+10] [Hz]
+Abcissa:       Channel
+Cursor:        Beam[0] IF[0] Pol[0]
+
+Scan Source         Position (J2000)        Time      Integration
+FreqIDs
+--------------------------------------------------------------------------------
+  0  NGC6334Fw      17:18:23.4 -35.47.01.3  17:30:37    1 x    02:31.0
+[0, 1] [0]
+  1  NGC6334F       17:20:53.4 -35.47.01.3  17:33:43    1 x    02:33.9
+[0, 1] [0]
+  2  NGC6334F       17:20:53.4 -35.47.01.3  17:36:34    1 x    02:33.9
+[2, 3] [0]
+  3  NGC6334Fe      17:23:23.4 -35.47.01.3  17:39:40    1 x    02:33.9
+[2, 3] [0]
+  4  NGC6334Fw      17:18:23.4 -35.47.01.3  17:42:11    1 x    01:22.9
+[4, 5] [0]
+
+Table contains 5 integration(s) in 5 scan(s).
+
+
+FreqID  Frame   RefFreq(Hz)     RefPix   Increment(Hz)
+--------------------------------------------------------------------------------
+0       TOPO    2.396822e+10    2048      3906.25
+1       TOPO    2.4143763e+10   2048      3906.25
+2       TOPO    2.3968219e+10   2048      3906.25
+3       TOPO    2.4143763e+10   2048      3906.25
+4       TOPO    2.3968219e+10   2048      3906.25
+5       TOPO    2.4143762e+10   2048      3906.25
+--------------------------------------------------------------------------------
+
+
+**BAD
+
+ASAP> scans = scantable('2005-03-09_172811_t002.rpf')
+Importing data...
+Auto averaging integrations...
+Weighting type selected : None
+
+ASAP> scans.summary(verbose=True)
+
+--------------------------------------------------------------------------------
+ Scan Table Summary
+--------------------------------------------------------------------------------
+Beams:         1
+IFs:           2
+Polarisations: 1
+Channels:      4096
+
+Observer:      Auto_Spec
+Obs Date:      2005/03/09/17:29:22
+Project:       NGC6334Fw
+Obs. Type:
+Antenna Name:  DSS-43
+Flux Unit:     K
+Rest Freqs:    [2.39639e+10] [Hz]
+Abcissa:       Channel
+Cursor:        Beam[0] IF[0] Pol[0]
+
+Scan Source         Position (J2000)        Time      Integration
+FreqIDs
+--------------------------------------------------------------------------------
+  0  NGC6334Fw      17:47:49.9 +16.00.27.6  17:30:37    1 x    02:31.0
+[0, 1] [0]
+  1  NGC6334F       17:03:08.2 -44.33.03.4  17:33:43    1 x    02:33.9
+[0, 1] [0]
+  2  NGC6334F       03:46:52.4 +56.43.05.6  17:36:34    1 x    02:33.9
+[2, 3] [0]
+  3  NGC6334Fe      17:47:49.9 +16.00.27.6  17:39:40    1 x    02:33.9
+[2, 3] [0]
+  4  NGC6334Fw      17:03:08.2 -44.33.03.4  17:42:11    1 x    01:22.9
+[4, 5] [0]
+
+Table contains 5 integration(s) in 5 scan(s).
+
+
+FreqID  Frame   RefFreq(Hz)     RefPix   Increment(Hz)
+--------------------------------------------------------------------------------
+0       TOPO    2.396822e+10    2048      3906.25
+1       TOPO    2.4143763e+10   2048      3906.25
+2       TOPO    2.3968219e+10   2048      3906.25
+3       TOPO    2.4143763e+10   2048      3906.25
+4       TOPO    2.3968219e+10   2048      3906.25
+5       TOPO    2.4143762e+10   2048      3906.25
+--------------------------------------------------------------------------------
+
+
+Seems to be a good old rpfits (fortran) memory problem.
+
+repeatedly opening an rpfits file with the read buggers up the refbeam field.
+as the summary function looks at the refbeam to determine the positions it 
+fails as the reader returns a "random" value.
+
+I have a chat to Mark.
+
+Malte.
+
+Mark,
+
+The attached data has only one beam.
+This only occurs on this data set.
+If I convert it to sdfits the problem is solved as you don't write this column 
+out unless it is MX data.
+For now I have put a kludge in, which checks nBeam >1 and only then gets the 
+refbeam from the data.
Index: tags/asap1/bugs/asap0001.txt
===================================================================
--- tags/asap1/bugs/asap0001.txt	(revision 819)
+++ tags/asap1/bugs/asap0001.txt	(revision 819)
@@ -0,0 +1,12 @@
+asap0001
+========
+Bug: restfrequency in header unreadable
+
+From: 
+Malte Marquarding <Malte.Marquarding@atnf.csiro.au>
+
+Date: 
+Today 09:55:07
+
+Change scantable.summary()
+to show restfreq with more (full) precision.
Index: tags/asap1/bugs/asap0002.txt
===================================================================
--- tags/asap1/bugs/asap0002.txt	(revision 819)
+++ tags/asap1/bugs/asap0002.txt	(revision 819)
@@ -0,0 +1,12 @@
+asap0002
+========
+Bug: list_scans list ipython variables
+
+From: 
+Malte Marquarding <Malte.Marquarding@atnf.csiro.au>
+
+
+Date: 
+Today 09:55:42
+
+Filter out all _* vars.
Index: tags/asap1/bugs/asap0003.txt
===================================================================
--- tags/asap1/bugs/asap0003.txt	(revision 819)
+++ tags/asap1/bugs/asap0003.txt	(revision 819)
@@ -0,0 +1,16 @@
+asap0003
+========
+Bug: quotient help not consistent
+
+From: 
+Malte Marquarding <Malte.Marquarding@atnf.csiro.au>
+
+
+To: 
+asap@atnf.csiro.au
+
+
+Date: 
+Today 09:56:36
+
+on/off and sig/ref used intermixed
Index: tags/asap1/bugs/asap0004.txt
===================================================================
--- tags/asap1/bugs/asap0004.txt	(revision 819)
+++ tags/asap1/bugs/asap0004.txt	(revision 819)
@@ -0,0 +1,15 @@
+asap0004
+========
+Bug: scantable.set_freqframe help doesn't list available frames
+
+From: 
+Malte Marquarding <Malte.Marquarding@atnf.csiro.au>
+
+
+To: 
+asap@atnf.csiro.au
+
+
+Date: 
+Today 09:57:32
+
Index: tags/asap1/bugs/asap0005.txt
===================================================================
--- tags/asap1/bugs/asap0005.txt	(revision 819)
+++ tags/asap1/bugs/asap0005.txt	(revision 819)
@@ -0,0 +1,17 @@
+asap0005
+========
+Bug: scantable.poly_baseline doesn't work
+
+From: 
+Malte Marquarding <Malte.Marquarding@csiro.au>  (ATNF)
+
+
+To: 
+asap@atnf.csiro.au
+
+
+Date: 
+Yesterday 15:16:10
+
+A simple paste error in the source code.
+There is some non-sensicla text pasted in right after the closing """
Index: tags/asap1/bugs/asap0006.txt
===================================================================
--- tags/asap1/bugs/asap0006.txt	(revision 819)
+++ tags/asap1/bugs/asap0006.txt	(revision 819)
@@ -0,0 +1,13 @@
+asap0006
+========
+
+enhancement/bugs from session with Malte on Mopra data
+
+From: 
+Juergen Ott <Juergen.Ott@atnf.csiro.au>
+
+
+To: 
+<asap@atnf.csiro.au>
+
+average_time loses unit km/s
Index: tags/asap1/bugs/asap0007.txt
===================================================================
--- tags/asap1/bugs/asap0007.txt	(revision 819)
+++ tags/asap1/bugs/asap0007.txt	(revision 819)
@@ -0,0 +1,14 @@
+asap0007
+========
+
+enhancement/bugs from session with Malte on Mopra data
+
+From: 
+Juergen Ott <Juergen.Ott@atnf.csiro.au>
+
+
+To: 
+<asap@atnf.csiro.au>
+
+help in average_pol has scantable argument
+
Index: tags/asap1/bugs/asap0008.txt
===================================================================
--- tags/asap1/bugs/asap0008.txt	(revision 819)
+++ tags/asap1/bugs/asap0008.txt	(revision 819)
@@ -0,0 +1,14 @@
+asap0008
+========
+
+enhancement/bugs from session with Malte on Mopra data
+
+From: 
+Juergen Ott <Juergen.Ott@atnf.csiro.au>
+
+
+To: 
+<asap@atnf.csiro.au>
+
+set_range in plotter doesn't default to ymin ymax if only x(min,max)
+are given
Index: tags/asap1/bugs/asap0009.txt
===================================================================
--- tags/asap1/bugs/asap0009.txt	(revision 819)
+++ tags/asap1/bugs/asap0009.txt	(revision 819)
@@ -0,0 +1,14 @@
+asap0009
+========
+
+enhancement/bugs from session with Malte on Mopra data
+
+From: 
+Juergen Ott <Juergen.Ott@atnf.csiro.au>
+
+
+To: 
+<asap@atnf.csiro.au>
+
+plotter.save: ps files seemed to be saved as eps (no document format
+displayed etc.)
Index: tags/asap1/bugs/asap0010.txt
===================================================================
--- tags/asap1/bugs/asap0010.txt	(revision 819)
+++ tags/asap1/bugs/asap0010.txt	(revision 819)
@@ -0,0 +1,14 @@
+asap0010
+========
+
+enhancement/bugs from session with Malte on Mopra data
+
+From: 
+Juergen Ott <Juergen.Ott@atnf.csiro.au>
+
+
+To: 
+<asap@atnf.csiro.au>
+
+
+scaling of data like: q*=2.0 doesn't work
Index: tags/asap1/bugs/asap0011.txt
===================================================================
--- tags/asap1/bugs/asap0011.txt	(revision 819)
+++ tags/asap1/bugs/asap0011.txt	(revision 819)
@@ -0,0 +1,14 @@
+asap0011
+========
+
+enhancement/bugs from session with Malte on Mopra data
+
+From: 
+Juergen Ott <Juergen.Ott@atnf.csiro.au>
+
+
+To: 
+<asap@atnf.csiro.au>
+
+
+plotter: if multipanel y-axis is not labelled
Index: tags/asap1/bugs/asap0012.txt
===================================================================
--- tags/asap1/bugs/asap0012.txt	(revision 819)
+++ tags/asap1/bugs/asap0012.txt	(revision 819)
@@ -0,0 +1,14 @@
+asap0012
+========
+
+enhancement/bugs from session with Malte on Mopra data
+
+From: 
+Juergen Ott <Juergen.Ott@atnf.csiro.au>
+
+
+To: 
+<asap@atnf.csiro.au>
+
+
+plotter: if multipanel text overlaps plots
Index: tags/asap1/bugs/asap0013.txt
===================================================================
--- tags/asap1/bugs/asap0013.txt	(revision 819)
+++ tags/asap1/bugs/asap0013.txt	(revision 819)
@@ -0,0 +1,14 @@
+asap0013
+========
+
+enhancement/bugs from session with Malte on Mopra data
+
+From: 
+Juergen Ott <Juergen.Ott@atnf.csiro.au>
+
+
+To: 
+<asap@atnf.csiro.au>
+
+
+plotter: set_title doesn't work properly
Index: tags/asap1/bugs/asap0014.txt
===================================================================
--- tags/asap1/bugs/asap0014.txt	(revision 819)
+++ tags/asap1/bugs/asap0014.txt	(revision 819)
@@ -0,0 +1,18 @@
+asap0014
+========
+Bug: Plotter takes a long time to replot on set_mode
+
+From: 
+Malte Marquarding <Malte.Marquarding@csiro.au>  (ATNF)
+
+
+To: 
+asap@atnf.csiro.au
+
+
+Date: 
+Today 13:27:23
+
+the last panel seems to be refreshing rather than being on "hold" and 
+"release"
+
Index: tags/asap1/bugs/asap0015.txt
===================================================================
--- tags/asap1/bugs/asap0015.txt	(revision 819)
+++ tags/asap1/bugs/asap0015.txt	(revision 819)
@@ -0,0 +1,7 @@
+asap0015
+========
+Bug: plotter doesn't plot more than 9 panels even when cursor is set
+
+From: 
+Malte Marquarding <Malte.Marquarding@csiro.au>  (ATNF)
+
Index: tags/asap1/bugs/asap0016.txt
===================================================================
--- tags/asap1/bugs/asap0016.txt	(revision 819)
+++ tags/asap1/bugs/asap0016.txt	(revision 819)
@@ -0,0 +1,7 @@
+asap0016 - CLOSED
+========
+Bug: plotter.set_legend doesn't work when "time" is selected
+
+From: 
+Malte Marquarding <Malte.Marquarding@csiro.au>  (ATNF)
+
Index: tags/asap1/bugs/asap0017.txt
===================================================================
--- tags/asap1/bugs/asap0017.txt	(revision 819)
+++ tags/asap1/bugs/asap0017.txt	(revision 819)
@@ -0,0 +1,8 @@
+asap0017
+========
+
+Bug: Fitter crashes during fit when set_function(gauss=n) was called with different n's
+
+From: 
+Malte Marquarding <Malte.Marquarding@csiro.au>  (ATNF)
+
Index: tags/asap1/bugs/asap0018.txt
===================================================================
--- tags/asap1/bugs/asap0018.txt	(revision 819)
+++ tags/asap1/bugs/asap0018.txt	(revision 819)
@@ -0,0 +1,14 @@
+asap0018
+========
+
+Bug: Missing template when buit against latest aips++
+
+From: 
+Malte Marquarding <Malte.Marquarding@csiro.au>  (ATNF)
+
+
+The template for 
+void Array<std::complex<float> >::reference(const Array<std::complex<float> >&)
+is missing from SDTemplates.cc
+
+Also, the footprint for median has changed.
Index: tags/asap1/bugs/asap0019.txt
===================================================================
--- tags/asap1/bugs/asap0019.txt	(revision 819)
+++ tags/asap1/bugs/asap0019.txt	(revision 819)
@@ -0,0 +1,8 @@
+asap0019
+========
+
+Bug: ordinate label in plotter is not adjuting title to unit e.g
+Brightness Temp <-> K
+ 
+
+
Index: tags/asap1/bugs/asap0020.txt
===================================================================
--- tags/asap1/bugs/asap0020.txt	(revision 819)
+++ tags/asap1/bugs/asap0020.txt	(revision 819)
@@ -0,0 +1,12 @@
+asap0020
+========
+Date: Sun, 8 May 2005 09:42:07 +1000 (EST)
+From: Malte Marquarding <Malte.Marquarding@atnf.csiro.au>
+To: asap@atnf.csiro.au
+Subject: Bug: SFITS written with asap is not read back in correctly by
+asap
+
+The scantable is corrupted and shows an extra row.
+ 
+
+
Index: tags/asap1/doc/CHANGELOG
===================================================================
--- tags/asap1/doc/CHANGELOG	(revision 819)
+++ tags/asap1/doc/CHANGELOG	(revision 819)
@@ -0,0 +1,60 @@
+Changelog
+=========
+
+(Release 1.2.2)
+
+* channel based flagging
+* Added get_elevation, azimuth and parangle
+* Added swap_pol & 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/asap1/doc/Makefile
===================================================================
--- tags/asap1/doc/Makefile	(revision 819)
+++ tags/asap1/doc/Makefile	(revision 819)
@@ -0,0 +1,32 @@
+TARGET := ./cookbook
+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).tex
+
+pdf: latex
+	$(DVIPDF) $(TARGET).dvi
+
+html:
+	$(LATEXHTML) $(LATEXHTMLOPTS) $(TARGET).tex
+
+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/asap1/doc/Plotting_Requirements.txt
===================================================================
--- tags/asap1/doc/Plotting_Requirements.txt	(revision 819)
+++ tags/asap1/doc/Plotting_Requirements.txt	(revision 819)
@@ -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/asap1/doc/README
===================================================================
--- tags/asap1/doc/README	(revision 819)
+++ tags/asap1/doc/README	(revision 819)
@@ -0,0 +1,27 @@
+This is a linux binary distribution of asap.
+
+To be able to run the following additional packages need to be installed.
+
+* gcc-3.3, g++-3.3
+* python2.3
+* boost_python (1.32)
+* lapack
+* blas
+* g2c
+* matplotlib (>0.73)
+* ipython 
+
+INSTALL
+=======
+
+To install simply run (as root or 'sudo')
+  ./bin/install.sh
+which will install asap into /usr/local
+
+
+RUN
+===
+
+simply type
+ asap
+at the prompt
Index: tags/asap1/doc/cookbook.tex
===================================================================
--- tags/asap1/doc/cookbook.tex	(revision 819)
+++ tags/asap1/doc/cookbook.tex	(revision 819)
@@ -0,0 +1,1763 @@
+\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 }
+\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.
+
+%\section{Documentation Standards}
+
+%In most of the examples in this document, it has been assumed that the
+
+\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).
+
+\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.  In the future, we will build a
+functional (non object oriented) shell on top of this to ease
+interactive use.
+
+\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{fitter}] A tool used to fit functions to the spectral data
+  \item[\cmd{plotter}] A tool used to plot the spectral line data
+  \item[\cmd{reader}] A tool which can be used to read data from disks
+    into a scantable object.
+\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(msk,weight='tsys')
+  ASAP> av = scans(mask=msk,weight='tsys')
+  ASAP> av = scans(msk,tsys)
+  ASAP> scans.polybaseline(mask=msk, order=0, insitu=True)
+  ASAP> scans.polybaseline(msk,0,True)
+  ASAP> scans.polybaseline(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>
+scans.set_cursor      scans.set_freqframe   scans.set_unit        scans.setpol
+scans.set_doppler     scans.set_instrument  scans.setbeam
+scans.set_fluxunit    scans.set_restfreqs   scans.setif
+  ASAP> scans.set_in<TAB>
+  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> ! 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
+  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 then 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 spectra 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.
+
+A collection of sequential integrations (rows) for one source is termed
+a scan (and each scan has a unique numeric identifier, the ScanID). A
+scantable is then a collection  of one or more scans. If you have
+scan-averaged your data in time, 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.
+
+\subsubsection {Contents}
+
+\index{Scantable!contents}A scantable has header information and data
+(a scantable is actually an AIPS++ Table and it is stored in Memory
+when you are manipulating it with ASAP.  You can store 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/integrations 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 FreqID and RestFreqID.  They contain indices, for
+each IF, pointing into tables with all of the frequency information for that
+integration.   More on these below when we discuss the \cmd{summary} function
+in the next subsection.
+
+There are of course many other columns which contain the actual spectra,
+the flags, the Tsys, the source names and so on, but those are a little
+more transparently handled.
+
+\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}
+
+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
+  ASAP> scans.summary(verbose=True)    # Include frequency information
+
+  # Equivalent to brief summary function call
+  ASAP> print scan
+\end{verbatim}
+
+Most of what the \cmd{summary} function  prints out is obvious. However,
+it also prints out the FreqIDs and RestFreqIDs to which we alluded above.
+These are the last column of the listing.
+
+The summary function gives you a scan-based summary.  So it lists all of
+the FreqIDs and RestFreqIDs that it encountered for each scan.  If you'd
+like to see what each FreqID actually means, then set the verbose
+argument to True and the frequency table will be listed at the end.
+FreqID of 3 say, refers to the fourth row of the frequency table (ASAP
+is 0-relative). The list of rest frequencies, to which the RestFreqIDs
+refer, is always listed.
+
+%You can copy one scantable to another with the \cmd{copy} function.
+
+%Example:
+
+%\begin{verbatim}
+%  ASAP> scans = scantable('MyData.rpf')
+%  ASAP> scan2 = scans.copy()
+%\end{verbatim}
+
+\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.
+
+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 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 selected for the
+specified source and IF and added to the list of rest frequencies.
+
+\begin{verbatim}
+  # Select for specified source/IF
+  ASAP> scans.set_restfreqs(freqs=1.667359e9, source='NGC253', theif=0)
+
+  # Select for all sources and IFs
+  ASAP> scans.set_restfreqs(freqs=1.667359e9)
+\end{verbatim}
+
+
+If you specify a list of frequencies, then it must be 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.  The
+internally stored list of rest frequencies will be replaced by this
+list.
+
+
+\begin{verbatim}
+  # Set rest frequency for all IFs
+  ASAP> scans.set_restfreqs(freqs=[1.6654018e9,1.667359e9,])
+
+\end{verbatim}
+
+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.lines()                 # Print list of known lines
+  ASAP> scans.set_restfreqs(lines=['OH1665','OH1667'])
+\end{verbatim}
+
+
+\subsection{Data Selection}
+
+\index{Scantable!data selection}Data selection is currently fairly limited. This
+will be improved in the future.
+
+
+\subsubsection{Cursor}
+
+\index{Scantable!cursor}Generally the user will want to run functions
+on all rows in a scantable. This allows very fast reduction of
+data. There are situations when functions should only operate on
+specific elements of the spectra. This is handled by the scantable
+cursor, which allows the user to select a single beam, IF and
+polarisation combination.
+
+Example :
+
+\begin{verbatim}
+  ASAP> scans.set_cursor(0,2,1)      # beam, IF, pol
+  ASAP> scans.smooth(allaxes=F)      # in situ by default or .aipsrc
+\end{verbatim}
+
+\subsubsection{Row number}
+
+\index{Scantable!row selection}Most functions work on all rows of a
+scan table. Exceptions are the fitter and plotter. If you wish to only
+operate on a selected set of scantable rows, use the \cmd{get\_scan}
+function to copy the rows into a new scantable.
+
+\subsubsection{Allaxes}
+
+\index{allaxes}\index{Scantable!allaxes}Many functions have an
+\cmd{allaxes} option which controls whether the function will operate
+on all elements within a scantable row, or just those selected with
+the current cursor. The default is taken from the users {\tt .asaprc}
+file.
+
+\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 cursor setting, selected row and
+selected frequency reference frame.
+
+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}
+
+
+\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 allow 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 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> s = r.read(range(100)) # To read in the first 100 integrations
+  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{Editing}
+
+%How and when?
+\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}
+
+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. 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.quotient(r, preserve=True)
+\end{verbatim}
+
+\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 = average_time(q)
+\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}
+
+Note that, if needed, you should run \cmd{freq\_align}, \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, 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}
+
+\cmd{freq\_align} has two modes of operations controlled by the
+\cmd{perif} argument. By default it will align each source and freqid
+separately. This is needed for scan tables containing multiple
+sources. However if scan-based Doppler tracking has been made at the
+observatory, each row will have a different freqid. In these cases run
+with \cmd{perif=True} and all rows of a source will be aligned to the
+same frame. In general \cmd{perif=True} will be needed for most
+observations as Doppler tracking of some form is made at Parkes, Tid
+and Mopra.
+
+\begin{verbatim}
+  ASAP> scans.freq_align(perif=True)
+\end{verbatim}
+
+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.
+
+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', stokes=True) # Convert raw polarisations into Stokes
+  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 plotter
+\cmd{set\_cursor} function is used to select a subset of the data. The
+arguments \cmd{row}, \cmd{beam} and \cmd{IF} all accept a vector of
+indices corresponding to row, beam or IF selection. Only the selected
+data will be plotted.  To select on polarisation, see
+section~\ref{sec:polplot}.
+
+Examples:
+
+\begin{verbatim}
+  # Select second IF
+  ASAP> plotter.set_cursor(IF=[1])
+
+  # Select first 4 beams
+  ASAP> plotter.set_cursor(beam=[0,1,2,3])
+
+  # Select a few rows
+  ASAP> plotter.set_cursor(row=[2,4,6,10])
+
+  # Multiple selection
+  ASAP> plotter.set_cursor(IF=[1], beam=[0,2], row=range(10))
+\end{verbatim}
+
+Note that the plotter cursor selection is independent of the scantable
+cursor.
+
+\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[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')
+\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 row/cursor selection or on an entire scantable using
+the \cmd{auto\_fit} function.
+
+\begin{verbatim}
+ ASAP> f = fitter()
+ ASAP> f.set_function(gauss=2) # Fit two Gaussians
+ ASAP> f.set_scan(scans)
+ ASAP> scans.set_cursor(0,0,1) # Fit the second polarisation
+ 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 basic.
+
+Conversions of linears to Stokes or Circular polarisations are done
+``on-the-fly''. Leakage cannot be corrected for nor are these routines
+able to calibrate position angle offsets.
+
+\subsection{Simple Calibration}
+
+{\em Currently the receiver position angle is not read from the RPFITS
+file and a position angle of zero is assumed. This severely hampers
+correct handling of polarimetry. In the future we aim to define a
+general framework and populate the RPFITS files with the data required
+for transparent polarimetric 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,
+either because of how it is mounted or if parallactifiying had to track
+at 90 degrees rather than 0. 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(-20) # Degrees; correct for receiver mounting
+
+  # Receiver was tracking 90 degrees rather than 0
+  ASAP> scans.rotate_linpolphase(90)
+\end{verbatim}
+
+\subsection{Plotting}
+\label{sec:polplot}
+
+\index{Polarisation!plotting}To plot Stokes values, the plotter
+\cmd{set\_cursor} function should be called first using the \cmd{pol}
+argument. 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). Conversion to circular polarisations
+are currently not available.
+
+Example:
+
+\begin{verbatim}
+  ASAP> plotter.set_cursor(pol=``I Q'')
+  ASAP> plotter.set_cursor(pol=``RR LL'')
+  ASAP> plotter.set_cursor(pol=``XX YY'')
+  ASAP> plotter.set_cursor(pol=``I Plinear'')
+\end{verbatim}
+
+Row, beam and IF selection are also available in \cmd{set\_cursor} as
+describe in section~\ref{sec:plotter_cursor}.
+
+\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 two scantables or a scantable and a float. (Note that it does
+not work for integers).
+
+\begin{verbatim}
+  ASAP> sum = scan1+scan2
+  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}
+# Concatenate the individual rpfits files together before loading
+!cat 2005-06*.rpf > data.rpf
+
+# Load the data into a scantable
+data = scantable('data.rpf')
+print data
+
+# Form the quotient spectra
+q = data.auto_quotient()
+print q
+
+# Look at the spectra
+plotter.plot(q)
+
+# Velocity align the data before averaging
+q.set_unit('km/s')
+q.set_freqframe('LSRK')
+q.freq_align()
+
+# Average all scans in time
+av = q.average_time()
+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) :
+
+ data.set_cursor(pol=0)
+ data.scale(xtsys/nomtsys,allaxes=False)
+
+ data.set_cursor(pol=1)
+ data.scale(ytsys/nomtsys,allaxes=False)
+
+ data.set_cursor(pol=2)
+ data.scale((xtsys+ytsys)/(2*nomtsys),allaxes=False)
+
+ data.set_cursor(pol=3)
+ data.scale((xtsys+ytsys)/(2*nomtsys),allaxes=False)
+\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(lines=['OH1667'])
+d1667.summary
+
+# Copy out the scan we wish to process
+g351_5 = d1665.get_scan('351p160')
+g351_7 = d1667.get_scan('351p160')
+
+# Plot the data
+plotter.plot(g351_5,g351_7) # 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(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)
+
+# Baseline the data
+msk = g351_5.create_mask([-20,-15],[0,5])
+g351_5.poly_baseline(msk,1)
+msk = g351_7.create_mask([-20,-15],[0,5])
+g351_7.poly_baseline(msk,1)
+
+# Update the plot with the baselined data
+plotter.plot()
+
+# Look at the various polarisation products
+plotter.set_cursor(pol='RR LL')
+plotter.set_cursor(pol='I Plinear')
+plotter.set_cursor(pol='I Q U V')
+
+# Save the plot as postscript
+plotter.save('g361_stokes.ps')
+
+# Save the process spectra
+g351_5.save('junk5.asap')
+g351_7.save('junk7.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
+
+# Plot/select in velocity
+q.set_freqframe('LSRK')
+q.set_unit('km/s')
+
+# Seperate data from the (1,1)&(2,2) and (4,4) transitions
+g1 = q.get_scan(range(6))     # Rows 0..5
+g2 = q.get_scan(range(6,12))  # Rows 6..11
+
+# Align data in velocity
+g1.freq_align(perif=True)
+g2.freq_align(perif=True)
+
+# Average individual scans
+a1 = g1.average_time()
+a2 = g2.average_time()
+
+# Rpfits file only contrains a single rest frequency. Set both
+a1.set_restfreqs(freqs= [23694.4700e6,23722.6336e6])
+
+plotter.plot(a1,a2)
+plotter.set_mode('i','s')
+x = raw_input()
+
+a1.auto_poly_baseline()
+a2.auto_poly_baseline()
+
+plotter.plot()
+
+a1.smooth('gauss',5)
+a2.smooth('gauss',5)
+plotter.plot()
+
+\end{verbatim}
+
+\newpage
+
+\section{Appendix}
+
+\subsection{Function Summary}
+
+\index{Functions!summary}%
+\begin{verbatim}
+    [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
+            summary         - print info about the scantable contents
+            set_cursor      - set a specific Beam/IF/Pol 'cursor' for
+                              further use
+            get_cursor      - print out the current cursor position
+            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_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 currnt 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_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
+            lines           - print list of known spectral lines
+            flag_spectrum   - flag a whole Beam/IF/Pol
+            save            - save the scantable to disk as either 'ASAP'
+                              or 'SDFITS'
+            nbeam,nif,nchan,npol - the number of beams/IFs/Pols/Chans
+            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.
+            auto_quotient   - return the on/off quotient with
+                              automatic detection of the on/off scans
+            quotient        - return the on/off quotient
+            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
+     [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 paramaters 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 (list of) 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_cursor      - only plot a selected part of the data
+            set_range       - set a 'zoom' window
+            set_legend      - specify user labels for the legend indeces
+            set_title       - specify user labels for the panel indeces
+            set_ordinate    - specify a user label for the ordinate
+            set_abcissa     - specify a user label for the abcissa
+            set_layout      - specify the multi-panel layout (rows,cols)
+
+    [Reading files]
+        reader              - access rpfits/sdfits files
+            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
+        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{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.
+
+\subsection{ASCII output format}
+
+\subsection{.asaprc settings}
+\index{.asaprc}
+\asaprc{verbose}{{\bf True}/False}{Print verbose output}
+
+\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}
+
+\asaprc{scantable.allaxes}{{\bf True}/False}{Apply action to all axes
+not just the cursor location}
+
+\asaprc{scantable.plotter}{{\bf True}/False}{Use internal plotter}
+
+\asaprc{scantable.verbosesummary}{True/{\bf False}}{Control the level
+of information printed by summary}
+
+\printindex
+
+\end{document}
+
Index: tags/asap1/doc/release-checklist.txt
===================================================================
--- tags/asap1/doc/release-checklist.txt	(revision 819)
+++ tags/asap1/doc/release-checklist.txt	(revision 819)
@@ -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/asap1/examples/test.py
===================================================================
--- tags/asap1/examples/test.py	(revision 819)
+++ tags/asap1/examples/test.py	(revision 819)
@@ -0,0 +1,60 @@
+#test the $Date$ field
+#import genera operating system related functions
+import os
+#import asap module
+from asap import *
+# create a readersa and open an RPFITS file
+r = reader('/u/mmarquar/zorro/singledish/data/2001-09-01_0332_P363.rpf')
+# create a vector with numbers [0..109]
+integrations = range(110)
+# get the data out of  the reader
+scans = r.read(integrations)
+# close the reader
+r = None
+print 'Begin export test...'
+# Write out the spectra in SDFITS format
+scans.save('/tmp/test_SDWriter.sdfits','SDFITS')
+# clean up
+print "removing test_SDWriter.sdfits ..."
+os.remove('/tmp/test_SDWriter.sdfits')
+# print a short summary of the data
+scans.summary()
+# get the scan with the number '0'
+scan = scans.get_scan(0)
+# get the scan with the name 'ref_R'
+ref = scans.get_scan('ref_R')
+# close the data table
+scans = None
+# open the math server
+# average the "on" scan
+scanav = average_time(scan)
+# get rid of the original scan
+scan = None
+# print a summary of the scan
+scanav.summary()
+# average the "off" scan
+refav = average_time(ref)
+# get rid of the original scan
+ref = None
+# print a summary of the scan
+refav.summary()
+# form the quotione spectrum
+quot = quotient(scanav,refav)
+# set the cursor to polarisation 0
+quot.set_cursor(pol=0)
+# get the spectrum for polarisation 0
+v0 = quot._getspectrum()
+#print  the first ten channel
+print v0[0:10]
+# set the cursor to polarisation 1
+quot.set_cursor(pol=1)
+# get the spectrum for polarisation 1
+v1 = quot._getspectrum()
+#print  the first ten channel
+print v1[0:10]
+# write it to disk for further use
+quot.save('/tmp/myfirstquotient.asap')
+# cleanup
+print "removing /tmp/myfirstquotient.asap ..."
+os.system('rm -rf /tmp/myfirstquotient.asap')
+print "Test successful."
Index: tags/asap1/monitor/README
===================================================================
--- tags/asap1/monitor/README	(revision 819)
+++ tags/asap1/monitor/README	(revision 819)
@@ -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/asap1/monitor/cgi-bin/asapconfig.py
===================================================================
--- tags/asap1/monitor/cgi-bin/asapconfig.py	(revision 819)
+++ tags/asap1/monitor/cgi-bin/asapconfig.py	(revision 819)
@@ -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/asap1/monitor/cgi-bin/asapmon.py
===================================================================
--- tags/asap1/monitor/cgi-bin/asapmon.py	(revision 819)
+++ tags/asap1/monitor/cgi-bin/asapmon.py	(revision 819)
@@ -0,0 +1,279 @@
+#!/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:
+            scans = [asap.scantable(f) for f in files]
+            outscans = []
+            self.fields['nif'] = range(scans[-1].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
+            for s in scans:
+                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)
+            for s in scans:
+                # 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]
+                # 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
+                            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"))
+
+                            m = s.create_mask(brange)
+                            s.poly_baseline(mask=m,order=order)
+                    else:
+                        s.auto_poly_baseline(order=order)
+                outscans.append(s)
+            del scans
+
+            if self.fields['average']:
+                outscans = asap.average_time(outscans,weight='tsys')
+            else:
+                outscans = outscans[-1]
+
+            if self.fields['bin']:
+                outscans.bin()
+
+            #duplicated as average_time doesn't remember settings
+            if self.fields['cunit'] == 1:
+                srest = outscans._getrestfreqs()
+                if isinstance(srest,tuple) and len(srest) != outscans.nif():
+                    outscans.set_restfreqs(restfs,unit="GHz")
+            outscans.set_unit(self.fields['units'][self.fields['cunit']])
+            outscans.set_freqframe(self.form.getfirst("frame","LSRK"))
+            outscans.set_doppler(self.form.getfirst("doppler","RADIO"))
+
+            self.fields['summary'] = str(outscans)
+            asap.rcParams['plotter.decimate'] = True
+            asap.rcParams['plotter.ganged'] = False
+            from matplotlib import rcParams as rcp
+            rcp['tick.labelsize'] = 6
+            rcp['axes.labelsize'] = 8
+            rcp['axes.titlesize'] = 8
+            rcp['figure.subplot.wspace'] = 0.3
+            rcp['figure.subplot.hspace'] = 0.3
+            del asap.plotter
+            asap.plotter = asap.asapplotter(False)
+            if s.nif() > 1:
+                asap.plotter.set_mode("p","i")
+            else:
+                if s.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"
+                asap.plotter.set_cursor(pol=pols)
+            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/asap1/monitor/cgi-bin/filelist.py
===================================================================
--- tags/asap1/monitor/cgi-bin/filelist.py	(revision 819)
+++ tags/asap1/monitor/cgi-bin/filelist.py	(revision 819)
@@ -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/asap1/monitor/cgi-bin/obsconfig.py
===================================================================
--- tags/asap1/monitor/cgi-bin/obsconfig.py	(revision 819)
+++ tags/asap1/monitor/cgi-bin/obsconfig.py	(revision 819)
@@ -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/asap1/monitor/htdocs/asapmon.css
===================================================================
--- tags/asap1/monitor/htdocs/asapmon.css	(revision 819)
+++ tags/asap1/monitor/htdocs/asapmon.css	(revision 819)
@@ -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/asap1/monitor/htdocs/asapmon.html.template
===================================================================
--- tags/asap1/monitor/htdocs/asapmon.html.template	(revision 819)
+++ tags/asap1/monitor/htdocs/asapmon.html.template	(revision 819)
@@ -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/asap1/monitor/htdocs/asapmon.js
===================================================================
--- tags/asap1/monitor/htdocs/asapmon.js	(revision 819)
+++ tags/asap1/monitor/htdocs/asapmon.js	(revision 819)
@@ -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/asap1/python/__init__.py
===================================================================
--- tags/asap1/python/__init__.py	(revision 819)
+++ tags/asap1/python/__init__.py	(revision 819)
@@ -0,0 +1,448 @@
+"""
+This is the ATNF Single Dish Analysis package.
+
+"""
+import os,sys
+
+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],
+
+    # scantable
+    'scantable.save'      : ['ASAP', str],
+    'scantable.autoaverage'      : [True, _validate_bool],
+    'scantable.freqframe' : ['LSRK', str],  #default frequency frame
+    'scantable.allaxes'   : [True, _validate_bool],  # apply action to all axes
+    'scantable.plotter'   : [True, _validate_bool], # use internal plotter
+    'scantable.verbosesummary'   : [False, _validate_bool]
+
+    # 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         :
+
+# scantable
+# default ouput format when saving
+scantable.save             : ASAP
+# auto averaging on read
+scantable.autoaverage      : True
+
+# default frequency frame to set when function
+# scantable.set_freqfrmae is called
+scantable.freqframe        : LSRK
+
+# apply action to all axes not just the cursor location
+scantable.allaxes          : True
+
+# use internal plotter
+scantable.plotter          : True
+
+# 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 Exception, 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):
+        out = True
+        for p in param:
+            out &= isinstance(p,ptype)
+        return out
+    elif isinstance(param, ptype):
+        return True
+    return False
+
+
+# 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
+
+from asapfitter import *
+from asapreader import reader
+
+from asapmath import *
+from scantable import *
+from asaplinefind import *
+from asapfit import *
+
+from numarray import logical_and as mask_and
+from numarray import logical_or as mask_or
+from numarray import logical_not as mask_not
+
+if rcParams['useplotter']:
+    from  asapplotter import *
+    gui = os.environ.has_key('DISPLAY') and rcParams['plotter.gui']
+    plotter = asapplotter(gui)
+    del gui
+
+__date__ = '$Date$'.split()[1]
+__version__  = '2.0a'
+
+if rcParams['verbose']:
+    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)
+            summary         - print info about the scantable contents
+            set_cursor      - set a specific Beam/IF/Pol 'cursor' for
+                              further use
+            get_cursor      - print out the current cursor position
+            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 currnt 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_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
+            lines           - print list of known spectral lines
+            flag_spectrum   - flag a whole Beam/IF/Pol
+            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 integrations 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.
+            auto_quotient   - return the on/off quotient with
+                              automatic detection of the on/off scans
+                              (matched pairs and 1 off - n on)
+            quotient        - return the on/off quotient
+            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 paramaters 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 (list of) 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_cursor      - 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
+            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
+        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
+
+            """
+        print x
+        return
+
+def welcome():
+    return """Welcome to ASAP v%s (%s) - the ATNF Spectral Analysis Package
+
+Please report any bugs to:
+asap@atnf.csiro.au
+
+[IMPORTANT: ASAP is 0-based]
+Type commands() to get a list of all available ASAP commands.""" % (__version__, __date__)
Index: tags/asap1/python/asapfit.py
===================================================================
--- tags/asap1/python/asapfit.py	(revision 819)
+++ tags/asap1/python/asapfit.py	(revision 819)
@@ -0,0 +1,73 @@
+from _asap import sdfit
+from asap import rcParams
+
+class asapfit(sdfit):
+
+    def __init__(self, other):
+        sdfit.__init__(self,other)
+
+    def __str__(self):
+        if self.__len__() == 0:
+            return "No fits"
+        out = ""
+        for i in range(self.__len__()):
+            out += "Fit No %d:" % (i)
+            pars = self.getparameters(i)
+            mask = self.getfixedparameters(i)
+            funcs = self.getfunctions(i)
+            comps = self.getcomponents(i)
+            finfo = self.getframeinfo(i)
+            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 = []
+        for i in range(self.__len__()):
+            pars = self.getparameters(i)
+            mask = self.getfixedparameters(i)
+            funcs = self.getfunctions(i)
+            comps = self.getcomponents(i)
+            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(i)
+                     }
+                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/asap1/python/asapfitter.py
===================================================================
--- tags/asap1/python/asapfitter.py	(revision 819)
+++ tags/asap1/python/asapfitter.py	(revision 819)
@@ -0,0 +1,488 @@
+import _asap
+from asap import rcParams
+from asap import print_log
+
+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:
+            from numarray import ones
+            self.mask = 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
+        if mask is None:
+            from numarray import ones
+            self.mask = 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):
+        """
+        Execute the actual fitting process. All the state has to be set.
+        Parameters:
+            row:    specify the row in the scantable
+        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:")
+                self.selection = self.data.get_cursor()
+        self.fitter.setdata(self.x, self.y, self.mask)
+        if self.fitfunc == 'gauss':
+            ps = self.fitter.getparameters()
+            if len(ps) == 0:
+                self.fitter.estimate()
+        try:
+            self.fitter.fit()
+        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())
+            self.data._addfit(self._fittedrow, pars, fixed,
+                              self.fitfuncs, self.components)
+
+    def set_parameters(self, params, fixed=None, component=None):
+        """
+        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
+             """
+        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:
+                from numarray import zeros
+                pars = list(zeros(len(self.components)*3))
+                fxd = list(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=False, centerfixed=False,
+                             fhwmfixed=False,
+                             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):
+            self.set_parameters([peak, centre, fhwm],
+                                [peakfixed, centerfixed, fhwmfixed],
+                                component)
+        else:
+            msg = "Please select a valid  component."
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise ValueError(msg)
+
+    def get_parameters(self, component=None):
+        """
+        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())
+        if component is not None:
+            if self.fitfunc == "gauss":
+                i = 3*component
+                cpars = pars[i:i+3]
+                cfixed = fixed[i:i+3]
+            else:
+                cpars = pars
+                cfixed = fixed
+        else:
+            cpars = pars
+            cfixed = fixed
+        fpars = self._format_pars(cpars, cfixed)
+        if rcParams['verbose']:
+            print fpars
+        return cpars, cfixed, fpars
+
+    def _format_pars(self, pars, fixed):
+        out = ''
+        if self.fitfunc == 'poly':
+            c = 0
+            for i in range(len(pars)):
+                fix = ""
+                if fixed[i]: fix = "(fixed)"
+                out += '  p%d%s= %3.3f,' % (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):
+                out += '  %d: 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)
+                c+=1
+                i+=3
+        return out
+
+    def get_estimate(self):
+        """
+        Return the parameter estimates (for non-linear functions).
+        """
+        pars = self.fitter.getestimate()
+        if rcParams['verbose']:
+            print self._format_pars(pars)
+        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:
+            print "Not yet fitted."
+            msg = "Not yet fitted."
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise RuntimeError(msg)
+        if self.data is not 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()
+
+    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'
+        m = ()
+        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","#bbbbbb","red","orange","purple","green","magenta", "cyan"]
+        self._p.palette(0,colours)
+        self._p.set_line(label='Spectrum')
+        self._p.plot(self.x, self.y, m)
+        if residual:
+            self._p.palette(1)
+            self._p.set_line(label='Residual')
+            self._p.plot(self.x, self.get_residual(), m)
+        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()[2]),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)
+                    self._p.plot(self.x, self.fitter.evaluate(c), m)
+                elif c == -1:
+                    self._p.palette(2)
+                    self._p.set_line(label="Total Fit")
+                    self._p.plot(self.x, self.get_fit(), m)
+        else:
+            self._p.palette(2)
+            self._p.set_line(label='Fit')
+            self._p.plot(self.x, self.get_fit(), m)
+        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, allaxes=True):
+        """
+        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
+        sel = scan.get_cursor()
+        rows = range(scan.nrow())
+        from asap import asaplog
+        if allaxes:
+            for i in range(scan.nbeam()):
+                scan.setbeam(i)
+                for j in range(scan.nif()):
+                    scan.setif(j)
+                    for k in range(scan.npol()):
+                        scan.setpol(k)
+                        asaplog.push("Fitting:")
+                        out = 'Beam[%d], IF[%d], Pol[%d]' % (i,j,k)
+                        asaplog.push(out)
+                        for iRow in rows:
+                            self.x = scan._getabcissa(iRow)
+                            self.y = scan._getspectrum(iRow)
+                            self.data = None
+                            self.fit()
+                            x = self.get_parameters()
+                            scan._setspectrum(self.fitter.getresidual(),iRow)
+        else:
+            asaplog.push("Fitting:")
+            out = 'Beam[%d], IF[%d], Pol[%d]' % sel
+            asaplog.push(out)
+            for iRow in rows:
+                self.x = scan._getabcissa(iRow)
+                self.y = scan._getspectrum(iRow)
+                self.data = None
+                self.fit()
+                x = self.get_parameters()
+                scan._setspectrum(self.fitter.getresidual(),iRow)
+
+        scan.set_cursor(sel[0],sel[1],sel[2])
+        print_log()
+        if not insitu:
+            return scan
Index: tags/asap1/python/asaplinefind.py
===================================================================
--- tags/asap1/python/asaplinefind.py	(revision 819)
+++ tags/asap1/python/asaplinefind.py	(revision 819)
@@ -0,0 +1,132 @@
+import _asap
+
+class linefinder:
+    """
+    The class for automated spectral line search in ASAP.
+
+    Example:
+       fl=linefinder()
+       fl.set_scan(sc,edge=(50,))
+       fl.set_options(threshold=3)
+       nlines=fl.find_lines()
+       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 reccommended 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,mask=None,edge=(0,0)):
+	"""
+	Set the 'data' (scantable) to work with.
+	Parameters:
+	     scan:    a scantable
+	     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
+        """
+	if not scan:
+	   raise RuntimeError, 'Please give a correct scan'
+ 
+	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"
+	if mask is None:
+	    from numarray import ones
+	    self.finder.setscan(scan,ones(scan.nchan()),tuple(edge))
+	else:    
+	    self.finder.setscan(scan,mask,tuple(edge))
+	return 
+    def find_lines(self,nRow=0):
+	"""
+	Search for spectral lines in the scan assigned in set_scan.
+	Current Beam/IF/Pol is used, Row is specified by parameter
+	A number of lines found will be returned
+	"""
+	return self.finder.findlines(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/asap1/python/asaplot.py
===================================================================
--- tags/asap1/python/asaplot.py	(revision 819)
+++ tags/asap1/python/asaplot.py	(revision 819)
@@ -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/asap1/python/asaplotbase.py
===================================================================
--- tags/asap1/python/asaplotbase.py	(revision 819)
+++ tags/asap1/python/asaplotbase.py	(revision 819)
@@ -0,0 +1,703 @@
+"""
+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
+from matplotlib.numerix import sqrt
+from matplotlib import rc, rcParams
+from asap import rcParams as asaprcParams
+
+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 1.
+	"""
+
+	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):
+	"""
+	Plot a histogram.  N.B. the x values refer to the start of the
+	histogram bin.
+
+	fmt is the line style as in plot().
+	"""
+
+	if x is None:
+	    if y is None: return
+	    x = range(0,len(y))
+
+	if len(x) != len(y):
+	    return
+
+	l2 = 2*len(x)
+	x2 = range(0,l2)
+	y2 = range(0,l2)
+
+	for i in range(0,l2):
+	    x2[i] = x[i/2]
+
+	y2[0] = 0
+	for i in range(1,l2):
+	    y2[i] = y[(i-1)/2]
+
+	self.plot(x2, y2, fmt)
+
+
+    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):
+            if 0 > loc > 10: loc = 0
+            self.loc = loc
+	self.show()
+
+
+    def plot(self, x=None, y=None, mask=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 mask is None:
+	    if fmt is None:
+		line = self.axes.plot(x, y)
+	    else:
+		line = self.axes.plot(x, y, fmt)
+	else:
+	    segments = []
+
+	    mask = list(mask)
+	    i = 0
+	    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(x[i:j])
+		segments.append(y[i:j])
+
+		i = j
+
+	    line = self.axes.plot(*segments)
+
+	# Add to an existing line?
+	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):
+	"""
+	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.
+	"""
+	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":
+                    w = self.figure.figwidth.get()
+                    h = self.figure.figheight.get()                        
+                    a4w = 8.25
+                    a4h = 11.25
+                    
+                    if orientation is None:
+                        # auto oriented
+                        if w > h:
+                            orientation = 'landscape'
+                        else:
+                            orientation = 'portrait'
+                    ds = None
+                    if orientation == 'landscape':
+                        ds = min(a4h/w,a4w/h)
+                    else:
+                        ds = min(a4w/w,a4h/h)
+                    ow = ds * w
+                    oh = ds * h
+                    self.figure.set_figsize_inches((ow,oh))
+                    self.canvas.print_figure(fname,orientation=orientation)
+                    print 'Written file %s' % (fname)
+                else:                    
+                    if dpi is None:
+                        dpi =150
+                    self.canvas.print_figure(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'
+
+	newargs = {}
+        
+	for k, v in kwargs.iteritems():
+	    k = k.lower()
+	    if k == 'colour': k = 'color'
+	    newargs[k] = v
+
+	getattr(self.axes, "set_%s"%what)(*args, **newargs)
+	self.show()
+
+
+    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()
+
+
+    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()
+
+
+    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
+
+	    for i in range(nplots):
+		self.subplots.append({})
+
+		self.subplots[i]['axes']  = self.figure.add_subplot(rows,
+						cols, i+1)
+		self.subplots[i]['lines'] = []
+                xfsize = self.subplots[i]['axes'].xaxis.label.get_size()-cols/2
+                yfsize = self.subplots[i]['axes'].yaxis.label.get_size()-rows/2
+                self.subplots[i]['axes'].xaxis.label.set_size(xfsize)
+                self.subplots[i]['axes'].yaxis.label.set_size(yfsize)
+                
+                if ganged:
+                    if rows > 1 or cols > 1:
+                        # Squeeze the plots together.
+                        pos = self.subplots[i]['axes'].get_position()
+                        if cols > 1: pos[2] *= 1.2
+                        if rows > 1: pos[3] *= 1.2
+                        self.subplots[i]['axes'].set_position(pos)
+
+                    # 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)
+                        
+
+		self.rows = rows
+		self.cols = cols
+
+	    self.subplot(0)
+
+    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):
+	"""
+	Show graphics dependent on the current buffering state.
+	"""
+	if not self.buffering:
+	    if self.loc is not None:
+		for j in range(len(self.subplots)):
+		    lines  = []
+		    labels = []
+		    i = 0
+		    for line in self.subplots[j]['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):
+			self.subplots[j]['axes'].legend(tuple(lines),
+							tuple(labels),
+							self.loc)
+		    else:
+			self.subplots[j]['axes'].legend((' '))
+
+
+    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()
Index: tags/asap1/python/asaplotgui.py
===================================================================
--- tags/asap1/python/asaplotgui.py	(revision 819)
+++ tags/asap1/python/asaplotgui.py	(revision 819)
@@ -0,0 +1,198 @@
+"""
+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('ASAPlot graphics window')
+
+	self.events = {'button_press':None,
+		       'button_release':None,
+		       'motion_notify':None}
+
+	matplotlib.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):
+	"""
+	Show graphics dependent on the current buffering state.
+	"""
+	if not self.buffering:
+            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/asap1/python/asapmath.py
===================================================================
--- tags/asap1/python/asapmath.py	(revision 819)
+++ tags/asap1/python/asapmath.py	(revision 819)
@@ -0,0 +1,107 @@
+from 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'
+    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')
+    varlist = vars()
+    if isinstance(args[0],list):
+        lst = tuple(args[0])
+    elif isinstance(args[0],tuple):
+        lst = args[0]
+    else:
+        lst = tuple(args)
+
+    del varlist["kwargs"]
+    varlist["args"] = "%d scantables" % len(lst)
+    # need special formatting her for history...
+
+    from asap._asap import average as _av
+    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(_av(lst, mask, scanAv, weight))
+    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 row, even if the signal has many. Otherwise
+    they must have the same number of rows.
+    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 quotient as _quot
+    s = scantable(_quot(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()
+    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)
+    from asap._asap import b_operate as _bop
+    s = scantable(_bop(left, right, op, tsys))
+    s._add_history("simple_math", varlist)
+    print_log()
+    return s
Index: tags/asap1/python/asapplotter.py
===================================================================
--- tags/asap1/python/asapplotter.py	(revision 819)
+++ tags/asap1/python/asapplotter.py	(revision 819)
@@ -0,0 +1,894 @@
+from asap import rcParams, print_log
+from numarray import logical_and
+
+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._tdict = {'Time':'t','time':'t','t':'t','T':'t'}
+        self._bdict = {'Beam':'b','beam':'b','b':'b','B':'b'}
+        self._idict = {'IF':'i','if':'i','i':'i','I':'i'}
+        self._pdict = {'Pol':'p','pol':'p','p':'p'}
+        self._sdict = {'scan':'s','Scan':'s','s':'s','S':'s'}
+        self._cdict = {'t':'len(self._cursor["t"])',
+                       'b':'len(self._cursor["b"])',
+                       'i':'len(self._cursor["i"])',
+                       'p':'len(self._cursor["p"])',
+                       's':'len(scans)'}
+        self._ldict = {'b':'Beam',
+                       'i':'IF',
+                       'p':'Pol',
+                       's':'Scan'}
+        self._dicts = [self._tdict,self._bdict,
+                       self._idict,self._pdict,
+                       self._sdict]
+        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._cursor = {'t':None, 'b':None,
+                        'i':None, 'p':None
+                        }
+        self._usermask = None
+        self._usermaskspectra = None
+
+    def _newplotter(self):
+        if self._visible:
+            from asap.asaplotgui import asaplotgui as asaplot
+        else:
+            from asap.asaplot import asaplot
+        return asaplot()
+
+
+    def _translate(self, name):
+        for d in self._dicts:
+            if d.has_key(name):
+                return d[name]
+        return None
+
+    def plot(self, *args):
+        """
+        Plot a (list of) scantables.
+        Parameters:
+            one or more comma separated scantables
+        Note:
+            If a (list) of scantables 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 scantables
+            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()
+        if len(args) > 0:
+            if self._data is not None:
+                if list(args) != self._data:
+                    self._data = list(args)
+                    # reset
+                    self._reset()
+            else:
+                if isinstance(args[0], list):
+                    self._data = args[0]
+                else:
+                    self._data = list(args)
+                self._reset()
+        # ranges become invalid when unit changes
+        if self._abcunit != self._data[0].get_unit():
+            self._minmaxx = None
+            self._minmaxy = None
+            self._abcunit = self._data[0].get_unit()
+            self._datamask = None
+        if self._panelling == 't':
+            maxrows = 25
+            if self._data[0].nrow() > maxrows and not (self._rows and self._cols):
+                if self._cursor["t"] is None or \
+                       (isinstance(self._cursor["t"],list) and \
+                        len(self._cursor["t"]) > maxrows ):
+                    from asap import asaplog
+                    msg ="Scan to be plotted contains more than %d rows.\n" \
+                          "Selecting first %d rows..." % (maxrows,maxrows)
+                    asaplog.push(msg)
+                    self._cursor["t"] = range(maxrows)
+            self._plot_time(self._data[0], self._stacking)
+        elif self._panelling == 's':
+            self._plot_scans(self._data, self._stacking)
+        else:
+            self._plot_other(self._data, self._stacking)
+        if self._minmaxy is not None:
+            self._plotter.set_limits(ylim=self._minmaxy)
+        self._plotter.release()
+        print_log()
+        return
+
+    def _plot_time(self, scan, colmode):
+        if colmode == 't':
+            return
+        n = len(self._cursor["t"])
+        cdict = {'b':'scan.setbeam(j)',
+                 'i':'scan.setif(j)',
+                 'p':'scan.setpol(j)'}
+        cdict2 = {'b':'self._cursor["b"]',
+                  'i':'self._cursor["i"]',
+                  'p':'self._cursor["p"]'}
+        ncol = 1
+        if self._stacking is not None:
+            ncol = eval(self._cdict.get(colmode))
+        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()
+        allxlim=[]
+        rows = self._cursor["t"]
+        rows = rows[:n]
+        self._plotter.palette(0)
+        for rowsel in rows:
+            i = self._cursor["t"].index(rowsel)
+            if n > 1:
+                self._plotter.palette(0)
+                self._plotter.subplot(i)
+            colvals = eval(cdict2.get(colmode))
+            for j in colvals:
+                polmode = "raw"
+                jj = colvals.index(j)
+                savej = j
+                for k in cdict.keys():
+                    sel = eval(cdict2.get(k))
+                    j = sel[0]
+                    if k == "p":
+                        which = self._cursor["p"].index(j)
+                        polmode = self._polmode[which]
+                        j = which
+                    eval(cdict.get(k))
+                j = savej
+                if colmode == "p":
+                    polmode = self._polmode[self._cursor["p"].index(j)]
+                    #j = jj
+                eval(cdict.get(colmode))
+                x = None
+                y = None
+                m = None
+                if self._title is None:
+                    tlab = scan._getsourcename(rowsel)
+                else:
+                    if len(self._title) >= n:
+                        tlab = self._title[rowsel]
+                    else:
+                        tlab = scan._getsourcename(rowsel)
+                x,xlab = scan.get_abcissa(rowsel)
+                if self._abcissa: xlab = self._abcissa
+                y = None
+                m = scan._getmask(rowsel)
+                if self._usermask and self._usermask.count(j):
+                    m  = logical_and(self._usermask, m)
+                if polmode == "stokes":
+                    y = scan._getstokesspectrum(rowsel)
+                elif polmode == "stokes2":
+                    y = scan._getstokesspectrum(rowsel,True)
+                elif polmode == "circular":
+                    y = scan._stokestopolspectrum(rowsel,False,-1)
+                else:
+                    y = scan._getspectrum(rowsel)
+                if self._ordinate:
+                    ylab = self._ordinate
+                else:
+                    ylab = scan._get_ordinate_label()
+                m = scan._getmask(rowsel)
+                if self._datamask is not None:
+                    if len(m) == len(self._datamask):
+                        m = logical_and(m,self._datamask)
+                if self._lmap and len(self._lmap) > 0:
+                    llab = self._lmap[jj]
+                else:
+                    if colmode == 'p':
+                        llab = self._get_pollabel(scan, polmode)
+                    else:
+                        llab = self._ldict.get(colmode)+' '+str(j)
+                self._plotter.set_line(label=llab)
+                if self._minmaxx is not None:
+                    s,e = self._slice_indeces(x)
+                    x = x[s:e]
+                    y = y[s:e]
+                    m = m[s:e]
+                if len(x) > 1024 and rcParams['plotter.decimate']:
+                    fac = len(x)/1024
+                    x = x[::fac]
+                    m = m[::fac]
+                    y = y[::fac]
+                self._plotter.plot(x,y,m)
+                xlim=[min(x),max(x)]
+                if self._minmaxx is not None:
+                    xlim = self._minmaxx
+                allxlim += xlim
+            allxlim.sort()
+            self._plotter.axes.set_xlim([allxlim[0],allxlim[-1]])
+            self._plotter.set_axes('xlabel',xlab)
+            self._plotter.set_axes('ylabel',ylab)
+            self._plotter.set_axes('title',tlab)
+
+        return
+
+    def _plot_scans(self, scans, colmode):
+        from asap import asaplog
+        msg = "Plotting mode is scans across panels. Can only plot one row per scan."
+        asaplog.push(msg)
+        if colmode == 's':
+            return
+        cdict = {'b':'scan.setbeam(j)',
+                 'i':'scan.setif(j)',
+                 'p':'scan.setpol(j)'}
+        cdict2 = {'b':'self._cursor["b"]',
+                  'i':'self._cursor["i"]',
+                  'p':'self._cursor["p"]'}
+
+        n = len(scans)
+        ncol = 1
+        if self._stacking is not None:
+            scan = scans[0]
+            ncol = eval(self._cdict.get(colmode))
+        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()
+
+        for scan in scans:
+            self._plotter.palette(0)
+            if n > 1:
+                self._plotter.subplot(scans.index(scan))
+            colvals = eval(cdict2.get(colmode))
+            rowsel = self._cursor["t"][0]
+            allxlim=[]
+            for j in colvals:
+                polmode = "raw"
+                jj = colvals.index(j)
+                savej = j
+                for k in cdict.keys():
+                    sel = eval(cdict2.get(k))
+                    j = sel[0]
+                    eval(cdict.get(k))
+                    if k == "p":
+                        which = self._cursor["p"].index(j)
+                        polmode = self._polmode[which]
+                        j = which
+                j = savej
+                if colmode == "p":
+                    polmode = self._polmode[self._cursor["p"].index(j)]
+                    #j = jj
+                eval(cdict.get(colmode))
+                x = None
+                y = None
+                m = None
+                tlab = self._title
+                if not self._title:
+                    tlab = scan._getsourcename(rowsel)
+                x,xlab = scan.get_abcissa(rowsel)
+                if self._abcissa: xlab = self._abcissa
+                if polmode == "stokes":
+                    y = scan._getstokesspectrum(rowsel)
+                elif polmode == "stokes2":
+                    y = scan._getstokesspectrum(rowsel,True)
+                elif polmode == "circular":
+                    y = scan._stokestopolspectrum(rowsel,False,-1)
+                else:
+                    y = scan._getspectrum(rowsel)
+                if self._ordinate:
+                    ylab = self._ordinate
+                else:
+                    ylab = scan._get_ordinate_label()
+                m = scan._getmask(rowsel)
+                if self._usermask and self._usermask.count(j):
+                    m  = logical_and(self._usermask, m)
+                if self._lmap and len(self._lmap) > 0:
+                    llab = self._lmap[jj]
+                else:
+                    if colmode == 'p':
+                        llab = self._get_pollabel(scan, polmode)
+                    else:
+                        llab = self._ldict.get(colmode)+' '+str(j)
+                self._plotter.set_line(label=llab)
+                if self._minmaxx is not None:
+                    s,e = self._slice_indeces(x)
+                    x = x[s:e]
+                    y = y[s:e]
+                    m = m[s:e]
+                if len(x) > 1024 and rcParams['plotter.decimate']:
+                    fac = len(x)/1024
+                    x = x[::fac]
+                    m = m[::fac]
+                    y = y[::fac]
+                self._plotter.plot(x,y,m)
+                xlim=[min(x),max(x)]
+                if self._minmaxx is not None:
+                    xlim = self._minmaxx
+                allxlim += xlim
+            allxlim.sort()
+            self._plotter.axes.set_xlim([allxlim[0],allxlim[-1]])
+            self._plotter.set_axes('xlabel',xlab)
+            self._plotter.set_axes('ylabel',ylab)
+            self._plotter.set_axes('title',tlab)
+        print_log()
+        return
+
+    def _plot_other(self,scans,colmode):
+        if colmode == self._panelling:
+            return
+        cdict = {'b':'scan.setbeam(i)',
+                 'i':'scan.setif(i)',
+                 'p':'scan.setpol(i)'}
+        cdict2 = {'b':'self._cursor["b"]',
+                  'i':'self._cursor["i"]',
+                  'p':'self._cursor["p"]',
+                  's': 'scans',
+                  't': 'self._cursor["t"]'}
+        scan = scans[0]
+        n = eval(self._cdict.get(self._panelling))
+        ncol=1
+        if self._stacking is not None:
+            ncol = eval(self._cdict.get(colmode))
+        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()
+        panels = self._cursor[self._panelling]
+        for i in panels:
+            self._plotter.palette(0)
+            polmode = "raw"
+            ii = self._cursor[self._panelling].index(i)
+            if n>1:
+                self._plotter.subplot(ii)
+            if self._panelling == "p":
+                polmode = self._polmode[ii]
+
+            eval(cdict.get(self._panelling))
+
+            allxlim=[]
+            colvals = eval(cdict2.get(colmode))
+            for j in colvals:
+                rowsel = self._cursor["t"][0]
+                jj = colvals.index(j)
+                savei = i
+                for k in cdict.keys():
+                    if k != self._panelling:
+                        sel = eval(cdict2.get(k))
+                        i = sel[0]
+                        if k == "p":
+                            which = self._cursor["p"].index(i)
+                            polmode = self._polmode[which]
+                            i = which
+                        eval(cdict.get(k))
+                i = savei
+                if colmode == 's':
+                    scan = j
+                elif colmode == 't':
+                    rowsel = j
+                else:
+                    savei = i
+                    if colmode == 'p':
+                        polmode = self._polmode[self._cursor["p"].index(j)]
+
+                    i = j
+                    eval(cdict.get(colmode))
+                    i = savei
+                #if self._panelling == "p":
+                eval(cdict.get(self._panelling))
+                x = None
+                y = None
+                m = None
+                x,xlab = scan.get_abcissa(rowsel)
+                if self._abcissa: xlab = self._abcissa
+                if polmode == "stokes":
+                    y = scan._getstokesspectrum(rowsel)
+                elif polmode == "stokes2":
+                    y = scan._getstokesspectrum(rowsel,True)
+                elif polmode == "circular":
+                    y = scan._stokestopolspectrum(rowsel,False,-1)
+                else:
+                    y = scan._getspectrum(rowsel)
+
+                if self._ordinate:
+                    ylab = self._ordinate
+                else:
+                    ylab = scan._get_ordinate_label()
+                m = scan._getmask(rowsel)
+                if self._usermask and self._usermask.count(j):
+                    m  = logical_and(self._usermask, m)
+
+                if colmode == 's' or colmode == 't':
+                    if self._title and len(self._title) > 0:
+                        tlab = self._title[ii]
+                    else:
+                        if self._panelling == 'p':
+                            tlab = self._get_pollabel(scan, polmode)
+                        else:
+                            tlab = self._ldict.get(self._panelling)+' '+str(i)
+                    if self._lmap and len(self._lmap) > 0:
+                        llab = self._lmap[jj]
+                    else:
+                        llab = scan._getsourcename(rowsel)
+                else:
+                    if self._title and len(self._title) > 0:
+                        tlab = self._title[ii]
+                    else:
+                        if self._panelling == 'p':
+                            tlab = self._get_pollabel(scan, polmode)
+                        else:
+                            tlab = self._ldict.get(self._panelling)+' '+str(i)
+                    if self._lmap and len(self._lmap) > 0:
+                        llab = self._lmap[jj]
+                    else:
+                        if colmode == 'p':
+                            llab = self._get_pollabel(scan, polmode)
+                        else:
+                            llab = self._ldict.get(colmode)+' '+str(j)
+                self._plotter.set_line(label=llab)
+                if self._minmaxx is not None:
+                    s,e = self._slice_indeces(x)
+                    x = x[s:e]
+                    y = y[s:e]
+                    m = m[s:e]
+                if len(x) > 1024 and rcParams['plotter.decimate']:
+                    fac = len(x)/1024
+                    x = x[::fac]
+                    m = m[::fac]
+                    y = y[::fac]
+                self._plotter.plot(x,y,m)
+                xlim=[min(x),max(x)]
+                if self._minmaxx is not None:
+                    xlim = self._minmaxx
+                allxlim += xlim
+            allxlim.sort()
+            self._plotter.axes.set_xlim([allxlim[0],allxlim[-1]])
+
+            self._plotter.set_axes('xlabel',xlab)
+            self._plotter.set_axes('ylabel',ylab)
+            self._plotter.set_axes('title',tlab)
+
+        return
+
+
+    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()
+        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()
+        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()
+        return
+
+    def set_legend(self, mp=None):
+        """
+        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}$'
+
+        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
+        if self._data: self.plot()
+        return
+
+    def set_title(self, title=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 self._data: self.plot()
+        return
+
+    def set_ordinate(self, ordinate=None):
+        """
+        Set the y-axis label of the plot. If multiple panels are plotted,
+        multiple labels have to be specified.
+        Example:
+             # two panels are visible on the plotter
+             plotter.set_ordinate(["First Y-Axis","Second Y-Axis"])
+        """
+        self._ordinate = ordinate
+        if self._data: self.plot()
+        return
+
+    def set_abcissa(self, abcissa=None):
+        """
+        Set the x-axis label of the plot. If multiple panels are plotted,
+        multiple labels have to be specified.
+        Example:
+             # two panels are visible on the plotter
+             plotter.set_ordinate(["First X-Axis","Second X-Axis"])
+        """
+        self._abcissa = abcissa
+        if self._data: self.plot()
+        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).
+        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()
+
+    def set_linestyles(self, linestyles):
+        """
+        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 self._data: self.plot()
+
+    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_cursor(self, row=None,beam=None,IF=None,pol=None, refresh=True):
+        """
+        Specify a 'cursor' for plotting selected spectra. Time (rows),
+        Beam, IF, Polarisation ranges can be specified.
+        Parameters:
+            Default for all paramaters is to select all available
+            row:    selects the rows (time stamps) to be plotted, this has
+                    to be a vector of row indices, e.g. row=[0,2,5] or row=[2]
+            beam:   select a range of beams
+            IF:     select a range of IFs
+            pol:    select Polarisations for plotting these can be by index
+                    (raw polarisations (default)) or by names any of:
+                    ["I", "Q", "U", "V"] or
+                    ["I", "Plinear", "Pangle", "V"] or
+                    ["XX", "YY", "Real(XY)", "Imag(XY)"] or
+                    ["RR", "LL"]
+        Example:
+            plotter.set_mode('pol','time')
+            plotter.plot(myscan) # plots all raw polarisations colour stacked
+            plotter.set_cursor(pol=["I"]) # plot "I" only for all rows
+            # plot "I" only for two time stamps row=0 and row=2
+            plotter.set_cursor(row=[0,2],pol=["I"])
+
+        Note:
+            Be careful to select only exisiting polarisations.
+        """
+        if not self._data:
+            msg = "Can only set cursor after a first call to plot()"
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise RuntimeError(msg)
+
+        n = self._data[0].nrow()
+        if row is None:
+            self._cursor["t"] = range(n)
+        else:
+            for i in row:
+                if i < 0 or i >= n:
+                    msg = "Row index '%d' out of range" % i
+                    if rcParams['verbose']:
+                        print msg
+                        return
+                    else:
+                        raise IndexError(msg)
+            self._cursor["t"] = row
+
+        n = self._data[0].nbeam()
+        if beam is None:
+            self._cursor["b"] = range(n)
+        else:
+            for i in beam:
+                if i < 0 or  i >= n:
+                    msg = "Beam index '%d' out of range" % i
+                    if rcParams['verbose']:
+                        print msg
+                        return
+                    else:
+                        raise IndexError(msg)
+
+            self._cursor["b"] = beam
+
+        n = self._data[0].nif()
+        if IF is None:
+            self._cursor["i"] = range(n)
+        else:
+            for i in IF:
+                if i < 0 or i >= n:
+                    msg = "IF index '%d' out of range" %i
+                    if rcParams['verbose']:
+                        print msg
+                        return
+                    else:
+                        raise IndexError(msg)
+            self._cursor["i"] = IF
+
+        n = self._data[0].npol()
+        dstokes = {"I":0,"Q":1,"U":2,"V":3}
+        dstokes2 = {"I":0,"Plinear":1,"Pangle":2,"V":3}
+        draw = {"XX":0, "YY":1,"Real(XY)":2, "Imag(XY)":3}
+        dcirc = { "RR":0,"LL":1}#,"Real(RL)":2,"Imag(RL)":3}
+
+        if pol is None:
+            self._cursor["p"] = range(n)
+            self._polmode = ["raw" for i in range(n)]
+        else:
+            if isinstance(pol,str):
+                pol = pol.split()
+            polmode = []
+            pols = []
+            for i in pol:
+                if isinstance(i,str):
+                    if draw.has_key(i):
+                        pols.append(draw.get(i))
+                        polmode.append("raw")
+                    elif dstokes.has_key(i):
+                        pols.append(dstokes.get(i))
+                        polmode.append("stokes")
+                    elif dstokes2.has_key(i):
+                        pols.append(dstokes2.get(i))
+                        polmode.append("stokes2")
+                    elif dcirc.has_key(i):
+                        pols.append(dcirc.get(i))
+                        polmode.append("circular")
+                    else:
+                        msg = "Pol type '%s' not valid" %i
+                        if rcParams['verbose']:
+                            print msg
+                            return
+                        else:
+                            raise TypeError(msg)
+                elif 0 > i >= n:
+                    print "Pol index '%d' out of range" %i
+                    if rcParams['verbose']:
+                        print msg
+                        return
+                    else:
+                        raise IndexError(msg)
+                else:
+                    pols.append(i)
+                    polmode.append("raw")
+            self._cursor["p"] = pols
+            self._polmode = polmode
+        if self._data and refresh: self.plot()
+
+    def set_mask(self, mask=None, pol=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
+             pol:      the polarisation to apply the mask to, e.g
+                       "Pangle" or "XX" etc.
+        Example:
+        """
+        if not self._data:
+            msg = "Can only set cursor after a first call to plot()"
+            if rcParams['verbose']:
+                print msg
+                return
+            else:
+                raise RuntimeError(msg)
+        if isinstance(mask, array):
+            self._usermask = mask
+        if isinstance(mask, list):
+            self._usermask = array(mask)
+        if mask is None and pol is None:
+            self._usermask = None
+            self._usermaskspectra = None
+
+        dstokes = {"I":0,"Q":1,"U":2,"V":3}
+        dstokes2 = {"I":0,"Plinear":1,"Pangle":2,"V":3}
+        draw = {"XX":0, "YY":1,"Real(XY)":2, "Imag(XY)":3}
+        dcirc = { "RR":0,"LL":1}#,"Real(RL)":2,"Imag(RL)":3}
+        if isinstance(pol, str):
+            pol = pol.split()
+        if isinstance(pol, list):
+            if isinstance(pol[0], str):
+                pass
+            else:
+                cpos = self._cursor[self._stacking]
+                self._usermaskspectra =filter(lambda i: filter(lambda j: j==i ,cpos),pol)
+        else:
+            return
+        self.plot()
+
+    def _get_pollabel(self, scan, polmode):
+        tlab = ""
+        if polmode == "stokes":
+            tlab = scan._getpolarizationlabel(0,1,0)
+        elif polmode == "stokes2":
+            tlab = scan._getpolarizationlabel(0,1,1)
+        elif polmode == "circular":
+            tlab = scan._getpolarizationlabel(0,0,0)
+        else:
+            tlab = scan._getpolarizationlabel(1,0,0)
+        return tlab
+
+    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 data[start] < mn:
+            start+= inc
+        # find max index
+        while data[end] > mx:
+            end-=inc
+        end +=1
+        if start > end:
+            return end,start
+        return start,end
+
+    def _reset(self):
+        self._usermask = None
+        self._usermaskspectra = None
+        self.set_cursor(refresh=False)
Index: tags/asap1/python/asapreader.py
===================================================================
--- tags/asap1/python/asapreader.py	(revision 819)
+++ tags/asap1/python/asapreader.py	(revision 819)
@@ -0,0 +1,93 @@
+from asap._asap import sdreader
+from asap import print_log
+
+class reader(sdreader):
+    """
+    This class allows the user to import single dish files
+    (rpfits,sdfits,ms).
+    The reader reads in integrations from the file and reamins at
+    the fileposition afterwards.
+    Available functions are:
+
+    read(integrations)
+    summary    CURRENTLY DISABLED
+
+    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)
+        sdreader.__init__(self, filename, theif, thebeam)
+        print_log()
+
+    def read(self,integrations=None):
+        """
+        Reads in an returns a specified sequence of integrations.
+        If no list is given all integrations a read in.
+        Parameters:
+            integrations:    a 'range' of integration numbers, e.g.
+                             range(100) or [0,1,2,3,4,10,11,100]
+                             If not given (default) all integrations
+                             are read in
+        Example:
+            r.read([0,1,2,3,4])    # reads in the first 5 integatrions
+                                   # NOT scans
+            r.read(range(100))     # read in the first 100 integrations
+        """
+        from asap import scantable
+        from asap import asaplog
+        if integrations is None:
+            integrations = [-1]
+        asaplog.push("Reading integrations from disk...")
+        sdreader._read(self,integrations)
+        tbl = sdreader._getdata(self)
+        sdreader._reset(self) # reset to the beginning of the file
+        if self.unit is not None:
+            tbl.set_fluxunit(self.unit)
+        print_log()
+        return scantable(tbl)
+
+    def summary(self, name=None):
+        """
+        Print a summary of all scans/integrations. This reads through the
+        whole file once.
+        Parameters:
+             None
+        Example:
+             r.summary()
+        """
+        sdreader._reset(self)
+        sdreader._read(self,[-1])
+        from asap import scantable
+        tbl = scantable(sdreader._getdata(self))
+        sdreader._reset(self)
+        tbl.summary(name)
+        return
Index: tags/asap1/python/scantable.py
===================================================================
--- tags/asap1/python/scantable.py	(revision 819)
+++ tags/asap1/python/scantable.py	(revision 819)
@@ -0,0 +1,1604 @@
+from asap._asap import sdtable
+from asap import rcParams
+from asap import print_log
+from numarray import ones,zeros
+import sys
+
+class scantable(sdtable):
+    """
+        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 or type(average) is not bool:
+            average = rcParams['scantable.autoaverage']
+
+        varlist = vars()
+        self._p = None
+        from asap import asaplog
+        if isinstance(filename,sdtable):
+            sdtable.__init__(self, filename)
+            if unit is not None:
+                self.set_fluxunit(unit)
+        else:
+            import os.path
+            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)
+            filename = os.path.expandvars(filename)
+            if os.path.isdir(filename):
+                # crude check if asap table
+                if os.path.exists(filename+'/table.info'):
+                    sdtable.__init__(self, filename)
+                    if unit is not None:
+                        self.set_fluxunit(unit)
+                else:
+                    msg = "The given file '%s'is not a valid asap table." % (filename)
+                    if rcParams['verbose']:
+                        print msg
+                        return
+                    else:
+                        raise IOError(msg)
+            else:
+                from asap._asap import sdreader
+                ifSel = -1
+                beamSel = -1
+                r = sdreader()
+                r._open(filename,ifSel,beamSel)
+                asaplog.push('Importing data...')
+                print_log()
+                r._read([-1])
+                tbl = r._getdata()
+                if unit is not None:
+                    tbl.set_fluxunit(unit)
+                if average:
+                    from asap._asap import average as _av
+                    asaplog.push('Auto averaging integrations...')
+                    print_log()
+                    tbl2 = _av((tbl,),(),True,'none')
+                    sdtable.__init__(self,tbl2)
+                    del tbl2
+                else:
+                    sdtable.__init__(self,tbl)
+                del r,tbl
+                self._add_history("scantable", varlist)
+        print_log()
+
+    def save(self, name=None, format=None, stokes=False, 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="FITS" this
+                         is the directory file name into which all the files
+                         will be written (default is 'asap_FITS'). 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)
+                                       'FITS' (saves each row as a FITS Image)
+                                       'ASCII' (saves as ascii text file)
+                                       'MS2' (saves as an aips++
+                                              MeasurementSet V2)
+            stokes:      Convert to Stokes parameters (only available
+                         currently with FITS and ASCII formats.
+                         Default is False.
+            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
+            from asap import asaplog
+            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 sdwriter as _sw
+            w = _sw(format2)
+            w.write(self, name, stokes)
+
+        print_log()
+        return
+
+    def copy(self):
+        """
+        Return a copy of this scantable.
+        Parameters:
+            none
+        Example:
+            copiedscan = scan.copy()
+        """
+        sd = scantable(sdtable._copy(self))
+        return sd
+
+    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:
+            if type(scanid) is str:
+                s = sdtable._getsource(self,scanid)
+                return scantable(s)
+            elif type(scanid) is int:
+                s = sdtable._getscan(self,[scanid])
+                return scantable(s)
+            elif type(scanid) is list:
+                s = sdtable._getscan(self,scanid)
+                return scantable(s)
+            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 sdtable._summary(self,True)
+
+    def summary(self,filename=None, verbose=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 = sdtable._summary(self, verbose)
+        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 set_cursor(self, beam=0, IF=0, pol=0):
+        """
+        Set the spectrum for individual operations.
+        Parameters:
+            beam, IF, pol:    a number
+        Example:
+            scan.set_cursor(0,0,1)
+            pol1sig = scan.stats(all=False) # returns std dev for beam=0
+                                            # if=0, pol=1
+        """
+        varlist = vars()
+        self.setbeam(beam)
+        self.setpol(pol)
+        self.setif(IF)
+        self._add_history("set_cursor",varlist)
+        return
+
+    def get_cursor(self):
+        """
+        Return/print a the current 'cursor' into the Beam/IF/Pol cube.
+        Parameters:
+            none
+        Returns:
+            a list of values (currentBeam,currentIF,currentPol)
+        Example:
+            none
+        """
+        i = self.getbeam()
+        j = self.getif()
+        k = self.getpol()
+        from asap import asaplog
+        out = "--------------------------------------------------\n"
+        out += " Cursor position\n"
+        out += "--------------------------------------------------\n"
+        out += 'Beam=%d IF=%d Pol=%d ' % (i,j,k)
+        asaplog.push(out)
+        print_log()
+        return i,j,k
+
+    def stats(self, stat='stddev', mask=None, allaxes=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.
+            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)
+        Example:
+            scan.set_unit('channel')
+            msk = scan.create_mask([100,200],[500,600])
+            scan.stats(stat='mean', mask=m)
+        """
+        if allaxes is None: allaxes = rcParams['scantable.allaxes']
+        from asap._asap import stats as _stats
+        from numarray import array,zeros,Float
+        if mask == None:
+            mask = ones(self.nchan())
+        axes = ['Beam','IF','Pol','Time']
+
+        beamSel,IFSel,polSel = (self.getbeam(),self.getif(),self.getpol())
+        if allaxes:
+            n = self.nbeam()*self.nif()*self.npol()*self.nrow()
+            shp = [self.nbeam(),self.nif(),self.npol(),self.nrow()]
+            arr = array(zeros(n),shape=shp,type=Float)
+
+            for i in range(self.nbeam()):
+                self.setbeam(i)
+                for j in range(self.nif()):
+                    self.setif(j)
+                    for k in range(self.npol()):
+                        self.setpol(k)
+                        arr[i,j,k,:] = _stats(self,mask,stat,-1)
+            retval = {'axes': axes, 'data': arr, 'cursor':None}
+            tm = [self._gettime(val) for val in range(self.nrow())]
+            if rcParams['verbose']:
+                self._print_values(retval,stat,tm)
+            self.setbeam(beamSel)
+            self.setif(IFSel)
+            self.setpol(polSel)
+            return retval
+
+        else:
+            statval = _stats(self,mask,stat,-1)
+            out = ''
+            for l in range(self.nrow()):
+                tm = self._gettime(l)
+                out += 'Time[%s]:\n' % (tm)
+                if self.nbeam() > 1: out +=  ' Beam[%d] ' % (beamSel)
+                if self.nif() > 1: out +=  ' IF[%d] ' % (IFSel)
+                if self.npol() > 1: out +=  ' Pol[%d] ' % (polSel)
+                out += '= %3.3f\n' % (statval[l])
+                out +=  "--------------------------------------------------\n"
+
+            if rcParams['verbose']:
+                print "--------------------------------------------------"
+                print " ",stat
+                print "--------------------------------------------------"
+                print out
+            retval = {'axes': axes, 'data': array(statval), 'cursor':(i,j,k)}
+            return retval
+
+    def stddev(self,mask=None, allaxes=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.
+            allaxes: optional flag to show all or a cursor selected
+                     spectrum of Beam/IF/Pol. Default is all or taken
+                     from .asaprc
+
+        Example:
+            scan.set_unit('channel')
+            msk = scan.create_mask([100,200],[500,600])
+            scan.stddev(mask=m)
+        """
+        if allaxes is None: allaxes = rcParams['scantable.allaxes']
+        return self.stats(stat='stddev',mask=mask, allaxes=allaxes);
+
+    def get_tsys(self, allaxes=None):
+        """
+        Return the System temperatures.
+        Parameters:
+           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)
+        Returns:
+            a list of Tsys values.
+        """
+        if allaxes is None: allaxes = rcParams['scantable.allaxes']
+        from numarray import array,zeros,Float
+        axes = ['Beam','IF','Pol','Time']
+
+        if allaxes:
+            n = self.nbeam()*self.nif()*self.npol()*self.nrow()
+            shp = [self.nbeam(),self.nif(),self.npol(),self.nrow()]
+            arr = array(zeros(n),shape=shp,type=Float)
+
+            for i in range(self.nbeam()):
+                self.setbeam(i)
+                for j in range(self.nif()):
+                    self.setif(j)
+                    for k in range(self.npol()):
+                        self.setpol(k)
+                        arr[i,j,k,:] = self._gettsys()
+            retval = {'axes': axes, 'data': arr, 'cursor':None}
+            tm = [self._gettime(val) for val in range(self.nrow())]
+            if rcParams['verbose']:
+                self._print_values(retval,'Tsys',tm)
+            return retval
+
+        else:
+            i,j,k = (self.getbeam(),self.getif(),self.getpol())
+            statval = self._gettsys()
+            out = ''
+            for l in range(self.nrow()):
+                tm = self._gettime(l)
+                out += 'Time[%s]:\n' % (tm)
+                if self.nbeam() > 1: out +=  ' Beam[%d] ' % (i)
+                if self.nif() > 1: out +=  ' IF[%d] ' % (j)
+                if self.npol() > 1: out +=  ' Pol[%d] ' % (k)
+                out += '= %3.3f\n' % (statval[l])
+                out +=  "--------------------------------------------------\n"
+
+            if rcParams['verbose']:
+                print "--------------------------------------------------"
+                print " TSys"
+                print "--------------------------------------------------"
+                print out
+            retval = {'axes': axes, 'data': array(statval), 'cursor':(i,j,k)}
+            return retval
+
+    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
+        """
+        out = []
+        if row == -1:
+            for i in range(self.nrow()):
+                out.append(self._gettime(i))
+            return out
+        else:
+            if row < self.nrow():
+                return self._gettime(row)
+
+    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
+        """
+        out = []
+        if row == -1:
+            return [self._getsourcename(i) for i in range(self.nrow())]
+        else:
+            if  0 <= row < self.nrow():
+                return 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
+        """
+        out = []
+        if row == -1:
+            return [self._getelevation(i) for i in range(self.nrow())]
+        else:
+            if  0 <= row < self.nrow():
+                return 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
+        """
+        out = []
+        if row == -1:
+            return [self._getazimuth(i) for i in range(self.nrow())]
+        else:
+            if  0 <= row < self.nrow():
+                return 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
+        """
+        out = []
+        if row == -1:
+            return [self._getparangle(i) for i in range(self.nrow())]
+        else:
+            if  0 <= row < self.nrow():
+                return self._getparangle(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)
+        if self._p: self.plot()
+        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_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)
+        if self._p: self.plot()
+        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 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 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 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.set_mask([400,500],[800,900])
+            # masks everything outside 400 and 500
+            # and 800 and 900 in the unit 'channel'
+
+            b)
+            msk = scan.set_mask([400,500],[800,900], invert=True)
+            # masks the regions between 400 and 500
+            # and 800 and 900 in the unit 'channel'
+
+        """
+        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"
+            from asap import asaplog
+            msg = "The current mask window unit is %s" % u
+            asaplog.push(msg)
+        n = self.nchan()
+        msk = 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'):
+                from numarray import logical_not
+                msk = 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 lines(self):
+        """
+        Print the list of known spectral lines
+        """
+        l = sdtable._lines(self)
+        if rcParams['verbose']:
+            print l
+        else:
+            return l
+
+    def set_restfreqs(self, freqs=None, unit='Hz', lines=None, source=None,
+                      theif=None):
+        """
+        Select the restfrequency for the specified source and IF OR
+        replace for all IFs.  If the 'freqs' argument holds a scalar,
+        then that rest frequency will be applied to the selected
+        data (and added to the list of available rest frequencies).
+        In this way, you can set a rest frequency for each
+        source and IF combination.   If the 'freqs' argument holds
+        a vector, then it MUST be of length the number of IFs
+        (and the available restfrequencies will be replaced by
+        this vector).  In this case, *all* data ('source' and
+        'theif' are ignored) 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 known line names
+        in the argument 'lines'.  Use 'freqs' or 'lines'.  'freqs'
+        takes precedence. See the list of known names via function
+        scantable.lines()
+        Parameters:
+            freqs:   list of rest frequencies
+            unit:    unit for rest frequency (default 'Hz')
+            lines:   list of known spectral lines names (alternative to freqs).
+                     See possible list via scantable.lines()
+            source:  Source name (blank means all)
+            theif:   IF (-1 means all)
+        Example:
+            scan.set_restfreqs(freqs=1.4e9, source='NGC253', theif=2)
+            scan.set_restfreqs(freqs=[1.4e9,1.67e9])
+        """
+        varlist = vars()
+        if source is None:
+            source = ""
+        if theif is None:
+            theif = -1
+        t = type(freqs)
+        if t is int or t is float:
+           freqs = [freqs]
+        if freqs is None:
+           freqs = []
+        t = type(lines)
+        if t is str:
+           lines = [lines]
+        if lines is None:
+           lines = []
+        sdtable._setrestfreqs(self, freqs, unit, lines, source, theif)
+        self._add_history("set_restfreqs", varlist)
+
+
+
+    def flag_spectrum(self, thebeam, theif, thepol):
+        """
+        This flags a selected spectrum in the scan 'for good'.
+        USE WITH CARE - not reversible.
+        Use masks for non-permanent exclusion of channels.
+        Parameters:
+            thebeam,theif,thepol:    all have to be explicitly
+                                     specified
+        Example:
+            scan.flag_spectrum(0,0,1)
+            flags the spectrum for Beam=0, IF=0, Pol=1
+        """
+        if (thebeam < self.nbeam() and
+            theif < self.nif() and
+            thepol < self.npol()):
+            sdtable.setbeam(self, thebeam)
+            sdtable.setif(self, theif)
+            sdtable.setpol(self, thepol)
+            sdtable._flag(self)
+            self._add_history("flag_spectrum", vars())
+        else:
+            print "Please specify a valid (Beam/IF/Pol)"
+        return
+
+    def _print_values(self, dat, label='', timestamps=[]):
+        d = dat['data']
+        a = dat['axes']
+        shp = d.getshape()
+        out = ''
+        for i in range(shp[3]):
+            out += '%s [%s]:\n' % (a[3],timestamps[i])
+            t = d[:,:,:,i]
+            for j in range(shp[0]):
+                if shp[0] > 1: out +=  ' %s[%d] ' % (a[0],j)
+                for k in range(shp[1]):
+                    if shp[1] > 1: out +=  ' %s[%d] ' % (a[1],k)
+                    for l in range(shp[2]):
+                        if shp[2] > 1: out +=  ' %s[%d] ' % (a[2],l)
+                        out += '= %3.3f\n' % (t[j,k,l])
+            out += "-"*80
+            out += "\n"
+        print "-"*80
+        print " ", label
+        print "-"*80
+        print out
+
+    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'):
+        """
+        Return the (time) average of a scan, or apply it 'insitu'.
+        Note:
+            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' (Tint/Tsys**2).
+                      The default is 'tint'
+        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 = ()
+        from asap._asap import average as _av
+        s = scantable(_av((self,), mask, scanav, weight))
+        s._add_history("average_time",varlist)
+        print_log()
+        return s
+
+    def convert_flux(self, jyperk=None, eta=None, d=None, insitu=None,
+                     allaxes=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 allaxes is None: allaxes = rcParams['scantable.allaxes']
+        if insitu is None: insitu = rcParams['insitu']
+        varlist = vars()
+        if jyperk is None: jyperk = -1.0
+        if d is None: d = -1.0
+        if eta is None: eta = -1.0
+        if not insitu:
+            from asap._asap import convertflux as _convert
+            s = scantable(_convert(self, d, eta, jyperk, allaxes))
+            s._add_history("convert_flux", varlist)
+            print_log()
+            return s
+        else:
+            from asap._asap import convertflux_insitu as _convert
+            _convert(self, d, eta, jyperk, allaxes)
+            self._add_history("convert_flux", varlist)
+            print_log()
+            return
+
+    def gain_el(self, poly=None, filename="", method="linear",
+                insitu=None, allaxes=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)
+            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 allaxes is None: allaxes = rcParams['scantable.allaxes']
+        if insitu is None: insitu = rcParams['insitu']
+        varlist = vars()
+        if poly is None:
+           poly = ()
+        from os.path import expandvars
+        filename = expandvars(filename)
+        if not insitu:
+            from asap._asap import gainel as _gainEl
+            s = scantable(_gainEl(self, poly, filename, method, allaxes))
+            s._add_history("gain_el", varlist)
+            print_log()
+            return s
+        else:
+            from asap._asap import gainel_insitu as _gainEl
+            _gainEl(self, poly, filename, method, allaxes)
+            self._add_history("gain_el", varlist)
+            print_log()
+            return
+
+    def freq_align(self, reftime=None, method='cubic', perif=False,
+                   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"
+            perif:       Generate aligners per freqID (no doppler tracking) or
+                         per IF (scan-based doppler tracking)
+            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']
+        varlist = vars()
+        if reftime is None: reftime = ''
+        perfreqid = not perif
+        if not insitu:
+            from asap._asap import freq_align as _align
+            s = scantable(_align(self, reftime, method, perfreqid))
+            s._add_history("freq_align", varlist)
+            print_log()
+            return s
+        else:
+            from asap._asap import freq_align_insitu as _align
+            _align(self, reftime, method, perfreqid)
+            self._add_history("freq_align", varlist)
+            print_log()
+            return
+
+    def opacity(self, tau, insitu=None, allaxes=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)
+            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 allaxes is None: allaxes = rcParams['scantable.allaxes']
+        if insitu is None: insitu = rcParams['insitu']
+        varlist = vars()
+        if not insitu:
+            from asap._asap import opacity as _opacity
+            s = scantable(_opacity(self, tau, allaxes))
+            s._add_history("opacity", varlist)
+            print_log()
+            return s
+        else:
+            from asap._asap import opacity_insitu as _opacity
+            _opacity(self, tau, allaxes)
+            self._add_history("opacity", varlist)
+            print_log()
+            return
+
+    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']
+        varlist = vars()
+        if not insitu:
+            from asap._asap import bin as _bin
+            s = scantable(_bin(self, width))
+            s._add_history("bin",varlist)
+            print_log()
+            return s
+        else:
+            from asap._asap import bin_insitu as _bin
+            _bin(self, width)
+            self._add_history("bin",varlist)
+            print_log()
+            return
+
+
+    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']
+        varlist = vars()
+        if not insitu:
+            from asap._asap import resample as _resample
+            s = scantable(_resample(self, method, width))
+            s._add_history("resample",varlist)
+            print_log()
+            return s
+        else:
+            from asap._asap import resample_insitu as _resample
+            _resample(self, method, width)
+            self._add_history("resample",varlist)
+            print_log()
+            return
+
+    def average_pol(self, mask=None, weight='none', insitu=None):
+        """
+        Average the Polarisations together.
+        The polarisation cursor of the output scan is set to 0
+        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)
+            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']
+        varlist = vars()
+
+        if mask is None:
+            mask = ()
+        if not insitu:
+            from asap._asap import averagepol as _avpol
+            s = scantable(_avpol(self, mask, weight))
+            s._add_history("average_pol",varlist)
+            print_log()
+            return s
+        else:
+            from asap._asap import averagepol_insitu as _avpol
+            _avpol(self, mask, weight)
+            self._add_history("average_pol",varlist)
+            print_log()
+            return
+
+    def smooth(self, kernel="hanning", width=5.0, insitu=None, allaxes=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)
+            allaxes:    If True (default) apply to all spectra. Otherwise
+                        apply only to the selected (beam/pol/if)spectra only
+                        The default is taken from .asaprc (True if none)
+        Example:
+             none
+        """
+        if allaxes is None: allaxes = rcParams['scantable.allaxes']
+        if insitu is None: insitu = rcParams['insitu']
+        varlist = vars()
+        if not insitu:
+            from asap._asap import smooth as _smooth
+            s = scantable(_smooth(self,kernel,width,allaxes))
+            s._add_history("smooth", varlist)
+            print_log()
+            return s
+        else:
+            from asap._asap import smooth_insitu as _smooth
+            _smooth(self,kernel,width,allaxes)
+            self._add_history("smooth", varlist)
+            print_log()
+            return
+
+    def poly_baseline(self, mask=None, order=0, insitu=None, allaxes=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)
+            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 (default) apply to all spectra. Otherwise
+                        apply only to the selected (beam/pol/if)spectra only
+                        The default is taken from .asaprc (True if none)
+        Example:
+            # return a scan baselined by a third order polynomial,
+            # not using a mask
+            bscan = scan.poly_baseline(order=3)
+        """
+        if allaxes is None: allaxes = rcParams['scantable.allaxes']
+        if insitu is None: insitu = rcParams['insitu']
+        varlist = vars()
+        if mask is None:
+            from numarray import ones
+            mask = list(ones(self.nchan()))
+        from asap.asapfitter import fitter
+        f = fitter()
+        f.set_scan(self, mask)
+        f.set_function(poly=order)
+        sf = f.auto_fit(insitu,allaxes)
+        if insitu:
+            self._add_history("poly_baseline", varlist)
+            print_log()
+            return
+        else:
+            sf._add_history("poly_baseline", varlist)
+            print_log()
+            return sf
+
+    def auto_poly_baseline(self, mask=None, edge=(0,0), order=0,
+                           threshold=3,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
+            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.
+            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
+
+        if not _is_valid(edge, int):
+
+            raise RuntimeError, "Parameter 'edge' has to be an integer or a \
+            pair of integers specified as a tuple"
+
+        # 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
+
+        sel=workscan.get_cursor()
+        rows=range(workscan.nrow())
+        from asap import asaplog
+        for i in range(workscan.nbeam()):
+            workscan.setbeam(i)
+            for j in range(workscan.nif()):
+                workscan.setif(j)
+                for k in range(workscan.npol()):
+                    workscan.setpol(k)
+                    asaplog.push("Processing:")
+                    msg = 'Beam[%d], IF[%d], Pol[%d]' % (i,j,k)
+                    asaplog.push(msg)
+                    for iRow in rows:
+                       fl.set_scan(workscan,mask,edge)
+                       fl.find_lines(iRow)
+                       f.set_scan(workscan, fl.get_mask())
+                       f.x=workscan._getabcissa(iRow)
+                       f.y=workscan._getspectrum(iRow)
+                       f.data=None
+                       f.fit()
+                       x=f.get_parameters()
+                       workscan._setspectrum(f.fitter.getresidual(),iRow)
+        workscan.set_cursor(sel[0],sel[1],sel[2])
+        if not insitu:
+            return workscan
+
+    def rotate_linpolphase(self, angle, allaxes=None):
+        """
+        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.
+            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)
+        Examples:
+            scan.rotate_linpolphase(2.3)
+    """
+        if allaxes is None: allaxes = rcParams['scantable.allaxes']
+        varlist = vars()
+        from asap._asap import _rotate_linpolphase as _rotate
+        _rotate(self, angle, allaxes)
+        self._add_history("rotate_linpolphase", varlist)
+        print_log()
+        return
+
+
+    def rotate_xyphase(self, angle, allaxes=None):
+        """
+        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.
+            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)
+        Examples:
+            scan.rotate_xyphase(2.3)
+        """
+        if allaxes is None: allaxes = rcParams['scantable.allaxes']
+        varlist = vars()
+        from asap._asap import _rotate_xyphase
+        _rotate_xyphase(self, angle, allaxes)
+        self._add_history("rotate_xyphase", varlist)
+        print_log()
+        return
+
+
+    def add(self, offset, insitu=None, allaxes=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)
+            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 allaxes is None: allaxes = rcParams['scantable.allaxes']
+        if insitu is None: insitu = rcParams['insitu']
+        varlist = vars()
+        if not insitu:
+            from asap._asap import add as _add
+            s = scantable(_add(self, offset, allaxes))
+            s._add_history("add",varlist)
+            print_log()
+            return s
+        else:
+            from asap._asap import add_insitu as _add
+            _add(self, offset, allaxes)
+            self._add_history("add",varlist)
+            print_log()
+            return
+
+    def scale(self, factor, insitu=None, allaxes=None, tsys=True):
+        """
+        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)
+            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)
+            tsys:        if True (default) then apply the operation to Tsys
+                         as well as the data
+        """
+        if allaxes is None: allaxes = rcParams['scantable.allaxes']
+        if insitu is None: insitu = rcParams['insitu']
+        varlist = vars()
+        if not insitu:
+            from asap._asap import scale as _scale
+            s = scantable(_scale(self, factor, allaxes, tsys))
+            s._add_history("scale",varlist)
+            print_log()
+            return s
+        else:
+            from asap._asap import scale_insitu as _scale
+            _scale(self, factor, allaxes, tsys)
+            self._add_history("scale",varlist)
+            print_log()
+            return
+
+    def auto_quotient(self, mode='suffix', 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 = Tref * (on/off) - Ton
+        """
+        modes = ["suffix","time"]
+        if not mode in modes:
+            print "please provide valid mode. Valid modes are %s" % (modes)
+            return None
+        from asap._asap import quotient as _quot
+        if mode == "suffix":
+            srcs = self.get_scan("*[^_ewR]")
+            refs = self.get_scan("*[_ewR]")
+            if isinstance(srcs,scantable) and isinstance(refs,scantable):
+                from asap import asaplog
+                ns,nr = srcs.nrow(),refs.nrow()
+                msg = "Found %i Off and %i On scans" % (ns,nr)
+                asaplog.push(msg)
+                if nr > ns:
+                    asaplog("Found more Off integrations than On scans - dropping excess Offs.")
+                    refs = refs.get_scan(range(ns))
+                print_log()
+                return scantable(_quot(srcs,refs, preserve))
+            else:
+                msg = "Couldn't find any on/off pairs"
+                if rcParams['verbose']:
+                    print msg
+                    return
+                else:
+                    raise RuntimeError()
+        else:
+            if rcParams['verbose']: print "not yet implemented"
+            return None
+
+    def quotient(self, other, isreference=True, preserve=True):
+        """
+        Return the quotient of a 'source' (on) scan and a 'reference' (off)
+        scan.
+        The reference can have just one row, even if the signal has many.
+        Otherwise they must have the same number of rows.
+        The cursor of the output scan is set to 0
+        Parameters:
+            other:          the 'other' scan
+            isreference:    if the 'other' scan is the reference (default)
+                            or source
+            preserve:       you can preserve (default) the continuum or
+                            remove it.  The equations used are
+                            preserve: Output = Toff * (on/off) - Toff
+                            remove:   Output = Tref * (on/off) - Ton
+        Example:
+            # src is a scantable for an 'on' scan, ref for an 'off' scantable
+            q1 = src.quotient(ref)
+            q2 = ref.quotient(src, isreference=False)
+            # gives the same result
+        """
+        from asap._asap import quotient as _quot
+        try:
+            s = None
+            if isreference:
+                s = scantable(_quot(self, other, preserve))
+            else:
+                s = scantable(_quot(other, self, preserve))
+            print_log()
+            return s
+        except RuntimeError,e:
+            if rcParams['verbose']:
+                print e
+                return
+            else: raise
+
+    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']
+        varlist = vars()
+        try:
+            if insitu:
+                from asap._asap import _frequency_switch_insitu
+                _frequency_switch_insitu(self)
+                self._add_history("freq_switch", varlist)
+                print_log()
+                return
+            else:
+                from asap._asap import _frequency_switch
+                sf = scantable(_frequency_switch(self))
+                sf._add_history("freq_switch", varlist)
+                print_log()
+                return sf
+        except RuntimeError,e:
+            if rcParams['verbose']: print e
+            else: raise
+
+    def recalc_azel(self):
+        """
+        Recalculate the azimuth and elevation for each position.
+        Parameters:
+            none
+        Example:
+        """
+        varlist = vars()
+        self._recalc_azel()
+        self._add_history("recalc_azel", varlist)
+        print_log()
+        return
+
+    def __add__(self, other):
+        varlist = vars()
+        s = None
+        if isinstance(other, scantable):
+            from asap._asap import b_operate as _bop
+            s = scantable(_bop(self, other, 'add', True))
+        elif isinstance(other, float):
+            from asap._asap import add as _add
+            s = scantable(_add(self, other, True))
+        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):
+            from asap._asap import b_operate as _bop
+            s = scantable(_bop(self, other, 'sub', True))
+        elif isinstance(other, float):
+            from asap._asap import add as _add
+            s = scantable(_add(self, -other, True))
+        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):
+            from asap._asap import b_operate as _bop
+            s = scantable(_bop(self, other, 'mul', True))
+        elif isinstance(other, float):
+            if other == 0.0:
+                raise ZeroDivisionError("Dividing by zero is not recommended")
+            from asap._asap import scale as _sca
+            s = scantable(_sca(self, other, True))
+        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):
+            from asap._asap import b_operate as _bop
+            s = scantable(_bop(self, other, 'div', True))
+        elif isinstance(other, float):
+            if other == 0.0:
+                raise ZeroDivisionError("Dividing by zero is not recommended")
+            from asap._asap import scale as _sca
+            s = scantable(_sca(self, 1.0/other, True))
+        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 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
+
Index: tags/asap1/share/ipythonrc-asap
===================================================================
--- tags/asap1/share/ipythonrc-asap	(revision 819)
+++ tags/asap1/share/ipythonrc-asap	(revision 819)
@@ -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/asap1/src/Makefile
===================================================================
--- tags/asap1/src/Makefile	(revision 819)
+++ tags/asap1/src/Makefile	(revision 819)
@@ -0,0 +1,175 @@
+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 := -O3 -fPIC
+CXXFLAGS += -ansi -pedantic -Wno-long-long
+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) $(BOOSTINC) $(PYTHONINC) $(CFITSIOINC)
+LIBS      := $(PYTHONLIB)  $(BOOSTLIB) $(CASAPPLIB)
+
+CCOBJECTS := MathUtils.o \
+             MathUtils2.o \
+	     SDLog.o \
+             SDAttr.o \
+             SDContainer.o \
+	     SDFitTable.o \
+             SDPol.o \
+             SDPol2.o \
+             SDMemTable.o \
+             SDReader.o \
+             SDWriter.o \
+             SDMath.o \
+             SDFitter.o \
+             SDTemplates.o \
+             SDMathWrapper.o \
+             SDFITSImageWriter.o \
+             SDAsciiWriter.o \
+	     SDLineFinder.o 
+PYOBJECTS := python_SDMemTable.o \
+             python_SDReader.o \
+             python_SDWriter.o \
+             python_SDMath.o \
+             python_SDFitter.o \
+             python_SDFitTable.o \
+	     python_SDLineFinder.o \
+	     python_SDLog.o \
+             python_SD.o
+
+OBJECTS := $(CCOBJECTS) $(PYOBJECTS)
+
+HEADERS   := SDDefs.h \
+             SDAttr.h \
+             SDContainer.h \
+	     SDFitTable.h \
+             SDMemTable.h \
+             SDPol.h \
+             SDReader.h \
+             SDWriter.h \
+             SDMath.h \
+             SDFitter.h \
+             SDMemTableWrapper.h \
+             SDMathWrapper.h \
+             SDReaderWrapper.h \
+             SDWriterWrapper.h \
+             SDFITSImageWriter.h \
+             SDAsciiWriter.h \
+	     SDLineFinder.h \
+	     SDLog.h \
+             python_SD.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
Index: tags/asap1/src/MathUtils.cc
===================================================================
--- tags/asap1/src/MathUtils.cc	(revision 819)
+++ tags/asap1/src/MathUtils.cc	(revision 819)
@@ -0,0 +1,130 @@
+//#---------------------------------------------------------------------------
+//# 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);
+   }
+}
+  
+
+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;
+   }
+}
+
+
+void mathutil::scanBoundaries(Vector<uInt>& startInt,
+			      Vector<uInt>& endInt,
+			      const Vector<Int>& scanIDs) 
+  // Find integrations start and end for each Scan
+{
+  uInt nInt = scanIDs.nelements();
+  startInt.resize(nInt);
+  endInt.resize(nInt);
+
+  startInt(0) = 0;
+  uInt j = 0;
+  Int currScanID = scanIDs(0);
+  for (uInt i=0; i<nInt; i++) {
+    if (scanIDs(i) != currScanID) {  
+      endInt(j) = i-1;
+      currScanID = scanIDs(i);
+
+      j += 1;
+      startInt(j) = i;
+      if (i==nInt-1) {
+	endInt(j) = i;
+      }   
+    } else {
+      if (i==nInt-1) endInt(j) = i;
+    }
+  }
+  startInt.resize(j+1,True);
+  endInt.resize(j+1,True);
+}
+
+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/asap1/src/MathUtils.h
===================================================================
--- tags/asap1/src/MathUtils.h	(revision 819)
+++ tags/asap1/src/MathUtils.h	(revision 819)
@@ -0,0 +1,77 @@
+//#---------------------------------------------------------------------------
+//# 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 {
+
+// Add new entry to list
+template <class T>
+casa::uInt addEntry(casa::Vector<T>& list, T val);
+
+// Find the scan boundaries from a list of ScanIDs
+void scanBoundaries(casa::Vector<casa::uInt>& startInt,  
+		    casa::Vector<casa::uInt>& endInt,
+		    const casa::Vector<casa::Int>& scanIDs);
+
+// Hanning smoothing
+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);
+
+// Extend the 
+template <class T>
+void extendLastArrayAxis(casa::Array<T>& out, const casa::Array<T>& in,
+			 const T& initVal);
+  
+std::vector<std::string> tovectorstring(const casa::Vector<casa::String>& in);
+
+casa::Vector<casa::String> toVectorString(const std::vector<std::string>& in);
+
+};
+
+#endif
Index: tags/asap1/src/MathUtils2.cc
===================================================================
--- tags/asap1/src/MathUtils2.cc	(revision 819)
+++ tags/asap1/src/MathUtils2.cc	(revision 819)
@@ -0,0 +1,131 @@
+//#---------------------------------------------------------------------------
+//# 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
+  uInt i = 1;
+  VectorSTLIterator<T> outit(out);
+  outit++;
+  VectorSTLIterator<Bool> outmit(outmask);outmit++;
+  uInt m;Vector<T>* w;
+  for (VectorSTLIterator<T> it = outit;it != out.end()-1;++it) {
+
+    m = mask[i-1] + 2*mask[i] + 4*mask[i+1];
+    w = &(weights[m]);
+    if (weighted[m]) {
+      (*it) = (*w)[0]*in[i-1] + (*w)[1]*in[i] + (*w)[2]*in[i+1];
+      (*outmit) = True;
+    } else { // mask it
+      (*outmit) = False;
+    }
+    ++i;
+    ++outmit;
+  }
+}
+
+template <class T>
+uInt mathutil::addEntry (Vector<T>& list, T val)
+{
+   uInt n = list.nelements();
+   if (n>0) {
+      for (uInt i=0; i<n; i++) {
+         if (near(list[i],val)) {
+            return i; 
+         }
+      }
+   }
+//
+   n++;
+   list.resize(n,True);
+   list(n-1) = val;
+   return n-1;
+}
+
+template <class T>
+void mathutil::extendLastArrayAxis(casa::Array<T>& out, 
+				   const casa::Array<T>& in,
+				   const T& initVal)
+{
+  
+  IPosition ipin = in.shape();
+  IPosition ipout = ipin; // copy?
+  // extend the axis by 1
+  uInt axis = in.ndim()-1;
+  ipout[axis] = ipin[axis]+1;
+  out.resize(ipout);
+  out = initVal;
+  ArrayIterator<T> itout(out,axis);
+  ReadOnlyArrayIterator<T> itin(in,axis);
+  while ( !itin.pastEnd() ) {
+    itout.array() = itin.array();  // copy vector by vector
+    itin.next(); itout.next();
+  }
+}
Index: tags/asap1/src/SDAsciiWriter.cc
===================================================================
--- tags/asap1/src/SDAsciiWriter.cc	(revision 819)
+++ tags/asap1/src/SDAsciiWriter.cc	(revision 819)
@@ -0,0 +1,216 @@
+//#---------------------------------------------------------------------------
+//# SDAsciiWriter.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/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/iostream.h>
+#include <casa/fstream.h>
+#include <casa/sstream.h>
+
+#include <coordinates/Coordinates/CoordinateUtil.h>
+#include <coordinates/Coordinates/SpectralCoordinate.h>
+#include <coordinates/Coordinates/DirectionCoordinate.h>
+#include <coordinates/Coordinates/StokesCoordinate.h>
+
+#include <measures/Measures/MEpoch.h>
+
+#include <tables/Tables/Table.h>
+#include <tables/Tables/ScalarColumn.h>
+#include <tables/Tables/ArrayColumn.h>
+
+#include "SDDefs.h"
+#include "SDContainer.h"
+#include "SDMemTable.h"
+#include "SDAsciiWriter.h"
+
+using namespace casa;
+using namespace asap;
+
+
+SDAsciiWriter::SDAsciiWriter()
+{;}
+
+SDAsciiWriter::~SDAsciiWriter()
+{;}
+
+
+Bool SDAsciiWriter::write(const SDMemTable& sdTable, const String& fileName, Bool toStokes)
+{
+
+// Get global Header from Table
+
+   SDHeader header = sdTable.getSDHeader();
+   MEpoch::Ref timeRef(MEpoch::UTC);              // Should be in header   
+   MDirection::Types dirRef(MDirection::J2000);   // Should be in header   
+
+// Column keywords
+
+   Table tab = sdTable.table();
+   ROArrayColumn<Double> dir(tab, String("DIRECTION"));
+   ROScalarColumn<Double> time(tab, "TIME");
+   ROArrayColumn<uInt> freqid(tab, "FREQID");
+   ROScalarColumn<String> src(tab, "SRCNAME");
+
+// Temps
+
+   Vector<Int> whichStokes(1,1);
+   Array<Double> whichDir;
+   Vector<Double> lonLat(2);
+   IPosition posDir(2,0);
+   const Unit RAD(String("rad"));
+
+// Open and write header file
+
+   String rootName(fileName);
+   if (rootName.length()==0) rootName = String("ascii");
+   {
+      String fName = String(rootName) + String("_header.txt");
+      pushLog("Writing header to "+fName);
+      ofstream of(fName.chars(), ios::trunc);
+      std::string summary = sdTable.summary(true);
+      of << summary;
+      of.close();
+   }
+
+// Open data file
+
+   String fName = rootName + String(".txt");
+   ofstream of(fName.chars(), ios::trunc);
+
+// Write header
+
+   of << "row beam IF pol source longitude latitude time nchan spectrum mask" 
+      << endl;
+   
+// Loop over rows
+
+   const uInt nRows = sdTable.nRow();
+   for (uInt iRow=0; iRow<nRows; iRow++) {
+
+// Get data
+
+      const MaskedArray<Float>& dataIn(sdTable.rowAsMaskedArray(iRow,toStokes));
+      const Array<Float>& values = dataIn.getArray();
+      const Array<Bool>& mask = dataIn.getMask();
+
+// Get abcissa
+
+      std::vector<double> abcissa = sdTable.getAbcissa(Int(iRow));
+      const uInt n = abcissa.size();
+
+// Iterate through data in this row by spectra
+
+      ReadOnlyVectorIterator<Float> itData(values, asap::ChanAxis);
+      ReadOnlyVectorIterator<Bool> itMask(mask, asap::ChanAxis);
+      while (!itData.pastEnd()) {
+         const IPosition& pos = itData.pos();
+         AlwaysAssert(itData.vector().nelements()==n,AipsError);
+
+// FreqID
+
+         Vector<uInt> iTmp;
+         freqid.get(iRow, iTmp);
+
+// Direction
+ 
+         dir.get(iRow, whichDir);
+         posDir(0) = pos(asap::BeamAxis);
+         posDir(1) = 0;
+         lonLat[0] = whichDir(posDir);
+//
+         posDir(0) = pos(asap::BeamAxis);
+         posDir(1) = 1;
+         lonLat[1] = whichDir(posDir);
+
+// Write preamble
+
+         of << iRow << "  " << pos(asap::BeamAxis) << " " <<  pos(asap::IFAxis) << " " << 
+               pos(asap::PolAxis) << " " <<
+               src(iRow) <<  " " << formatDirection(lonLat) << " " << 
+               sdTable.getTime(iRow,True) << " " << n << " ";
+
+// Write abcissa
+
+         of.setf(ios::fixed, ios::floatfield);
+         of.precision(4);
+         for (uInt i=0; i<n; i++) {
+            of << abcissa[i] << " ";
+         }
+
+// Write data
+
+         const Vector<Float>& data = itData.vector();
+         const Vector<Bool>& mask = itMask.vector();
+         for (uInt i=0; i<n; i++) {
+            of << data[i] << " ";
+         }
+// Write mask
+
+         for (uInt i=0; i<n; i++) {
+            of << mask[i] << " ";
+         }
+         of << endl;
+
+// Next spectrum
+
+         itData.next();
+         itMask.next();
+      }
+   }
+
+   of.close();
+   ostringstream oss;
+   oss << "Wrote " << nRows << " rows into file " << fileName;
+   pushLog(String(oss));
+   return True;
+}
+
+
+
+String SDAsciiWriter::formatDirection(const Vector<Double>& lonLat)
+{ 
+   MVAngle x1(lonLat(0));
+   String s1 = x1.string(MVAngle::TIME, 12);
+
+   MVAngle x2(lonLat(1));
+   String s2 = x2.string(MVAngle::ANGLE, 12);
+
+   String ss = s1 + String(" ") + s2;
+   return ss;
+}
+
Index: tags/asap1/src/SDAsciiWriter.h
===================================================================
--- tags/asap1/src/SDAsciiWriter.h	(revision 819)
+++ tags/asap1/src/SDAsciiWriter.h	(revision 819)
@@ -0,0 +1,62 @@
+//#---------------------------------------------------------------------------
+//# SDAsciiWriter.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 SDASCIIWRITER_H
+#define SDASCIIWRITER_H
+
+#include <casa/aips.h>
+#include <casa/BasicSL/String.h>
+#include <SDMemTable.h>
+
+#include "SDLog.h"
+
+template<class T> class casa::Vector;
+
+
+namespace asap {
+
+class SDAsciiWriter : public SDLog {
+public:
+// Constructor
+  SDAsciiWriter();
+
+// Destructor
+  ~SDAsciiWriter();
+
+// Write out ascii table
+  casa::Bool write(const SDMemTable& table, const casa::String& name, casa::Bool toStokes);
+
+private:
+   casa::Int convertStokes(casa::Int val);
+   casa::String formatDirection(const casa::Vector<casa::Double>& lonLat);
+};
+
+}// namespace
+#endif
Index: tags/asap1/src/SDAttr.cc
===================================================================
--- tags/asap1/src/SDAttr.cc	(revision 819)
+++ tags/asap1/src/SDAttr.cc	(revision 819)
@@ -0,0 +1,346 @@
+//#---------------------------------------------------------------------------
+//# SDAttr.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 "SDAttr.h"
+
+using namespace casa;
+using namespace asap;
+
+SDAttr::SDAttr()
+{
+   initData();
+}
+
+SDAttr::SDAttr(const SDAttr& other)
+{
+   initData();                     // state just private 'static' data
+}
+
+SDAttr& SDAttr::operator=(const SDAttr& other) 
+{
+  if (this != &other) {
+    ;                             // state just private 'static' data
+  }
+  return *this;
+}
+
+SDAttr::~SDAttr()
+{;}
+
+
+Float SDAttr::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> SDAttr::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> SDAttr::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> SDAttr::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) = SDAttr::findJyPerK(etaAp(i), D);
+   }
+   return facs;
+}
+
+
+Float SDAttr::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> SDAttr::gainElevationPoly(Instrument inst) const
+{
+
+  // Look at date where appropriate
+  switch (inst) {
+  case TIDBINBILLA:
+    {
+      return TidGainElPoly_.copy();
+    }
+    break;
+  default:
+    {
+      Vector<Float> t;
+      return t.copy();
+    }
+  }
+}
+
+FeedPolType SDAttr::feedPolType(Instrument inst) const
+{
+  FeedPolType type = UNKNOWNFEED;
+  switch (inst) {
+  case ATMOPRA:
+  case ATPKSMB:
+  case ATPKSHOH:
+    {
+      type = LINEAR;
+    }
+    break;
+  case TIDBINBILLA:
+    {
+      type = CIRCULAR;
+    }
+    break;
+  default:
+    {
+      type = UNKNOWNFEED;
+    }
+  }
+  return type;
+}
+
+
+// Private
+Vector<Float> SDAttr::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 SDAttr::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 SDAttr::convertInstrument(const String& instrument,
+                                     Bool throwIt)
+{
+  String t(instrument);
+  t.upcase();
+  
+  // The strings are what SDReader 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/asap1/src/SDAttr.h
===================================================================
--- tags/asap1/src/SDAttr.h	(revision 819)
+++ tags/asap1/src/SDAttr.h	(revision 819)
@@ -0,0 +1,127 @@
+//#---------------------------------------------------------------------------
+//# SDAttr.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 SDATTR_H
+#define SDATTR_H
+
+#include <casa/aips.h>
+#include <measures/Measures/MEpoch.h>
+#include <casa/Utilities/CountedPtr.h>
+
+#include "SDLog.h"
+#include "SDDefs.h"
+
+template<class T> class casa::Vector;
+
+
+namespace asap {
+
+class SDAttr : public SDLog {
+
+ public:
+
+  // Constructor
+  SDAttr();
+  
+  // Destructor
+  ~SDAttr();
+  
+  // Copy Constructor (copy semantics)
+  SDAttr(const SDAttr& other);
+  
+  // Assignment  (copy semantics)
+  SDAttr &operator=(const SDAttr& 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
+  FeedPolType 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/asap1/src/SDContainer.cc
===================================================================
--- tags/asap1/src/SDContainer.cc	(revision 819)
+++ tags/asap1/src/SDContainer.cc	(revision 819)
@@ -0,0 +1,623 @@
+//#---------------------------------------------------------------------------
+//# SDContainer.cc: 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 <tables/Tables/Table.h>
+#include <casa/Arrays/IPosition.h>
+#include <casa/Arrays/Matrix.h>
+#include <casa/Arrays/VectorIter.h>
+#include <casa/Arrays/ArrayMath.h>
+#include <casa/BasicMath/Math.h>
+#include <casa/Quanta/MVTime.h>
+
+
+
+#include "SDDefs.h"
+#include "SDContainer.h"
+
+using namespace casa;
+using namespace asap;
+
+void SDHeader::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;
+}
+
+
+SDContainer::SDContainer(uInt nBeam, uInt nIF, uInt nPol, uInt nChan) 
+  : nBeam_(nBeam),
+    nIF_(nIF),
+    nPol_(nPol),
+    nChan_(nChan),
+    spectrum_(IPosition(4,nBeam,nIF,nPol,nChan)),
+    flags_(IPosition(4,nBeam,nIF,nPol,nChan)),
+    tsys_(IPosition(4,nBeam,nIF,nPol,nChan)),
+    freqidx_(nIF),
+    restfreqidx_(nIF),
+    direction_(IPosition(2,nBeam,2)) {
+  uChar x = 0;
+  flags_ = ~x;
+  tcal.resize(2);
+}
+
+SDContainer::SDContainer(IPosition shp) 
+  : nBeam_(shp(asap::BeamAxis)),
+    nIF_(shp(asap::IFAxis)),
+    nPol_(shp(asap::PolAxis)),
+    nChan_(shp(asap::ChanAxis)),
+    spectrum_(shp),
+    flags_(shp),
+    tsys_(shp),
+    freqidx_(shp(asap::IFAxis)),
+    restfreqidx_(shp(asap::IFAxis)) {
+  IPosition ip(2,shp(asap::BeamAxis),2);
+  direction_.resize(ip);
+  uChar x = 0;
+  flags_ = ~x;
+  tcal.resize(2);
+}
+
+SDContainer::~SDContainer() {
+}
+
+Bool SDContainer::resize(IPosition shp) {
+  nBeam_ = shp(asap::BeamAxis);
+  nIF_ = shp(asap::IFAxis);
+  nPol_ = shp(asap::PolAxis);
+  nChan_ = shp(asap::ChanAxis);
+  spectrum_.resize(shp);
+  flags_.resize(shp);
+  tsys_.resize(shp);
+  freqidx_.resize(shp(asap::IFAxis));
+  restfreqidx_.resize(shp(asap::IFAxis));
+  IPosition ip(2,shp(asap::BeamAxis),2);
+  direction_.resize(ip);
+}
+
+Bool SDContainer::putSpectrum(const Array<Float>& spec) {
+  spectrum_ = spec;
+}
+Bool SDContainer::putFlags(const Array<uChar>& flag) {
+  flags_ = flag;
+}
+Bool SDContainer::putTsys(const Array<Float>& tsys) {
+  tsys_ = tsys;
+}
+
+Bool SDContainer::setSpectrum(const Matrix<Float>& spec,
+                              const Vector<Complex>& cSpec,
+			      uInt whichBeam, uInt whichIF) 
+//
+// spec is [nChan,nPol] 
+// cspec is [nChan]
+// spectrum_ is [,,,nChan]
+//
+// nPOl_ = 4  - xx, yy, real(xy), imag(xy)
+//
+{
+  AlwaysAssert(nPol_==4,AipsError);
+
+// Get slice  and check dim
+
+  Bool tSys = False;
+  Bool xPol = True;
+  IPosition start, end;
+  setSlice(start, end, spec.shape(), spectrum_.shape(),
+            whichBeam, whichIF, tSys, xPol);
+
+// Get a reference to the Pol/Chan slice we are interested in
+
+  Array<Float> subArr = spectrum_(start,end);
+
+// Iterate through pol-chan plane and fill
+
+  ReadOnlyVectorIterator<Float> inIt(spec,0);
+  VectorIterator<Float> outIt(subArr,asap::ChanAxis);
+  while (!outIt.pastEnd()) {
+     const IPosition& pos = outIt.pos();
+     if (pos(asap::PolAxis)<2) {
+        outIt.vector() = inIt.vector();
+        inIt.next();
+     } else if (pos(asap::PolAxis)==2) {
+        outIt.vector() = real(cSpec);
+     } else if (pos(asap::PolAxis)==3) {
+        outIt.vector() = imag(cSpec);
+     }
+//
+     outIt.next();
+  }
+
+  // unset flags for this spectrum, they might be set again by the
+  // setFlags method
+
+  Array<uChar> arr(flags_(start,end));
+  arr = uChar(0);
+//
+  return True;
+}
+
+
+Bool SDContainer::setSpectrum(const Matrix<Float>& spec,
+			      uInt whichBeam, uInt whichIF) 
+//
+// spec is [nChan,nPol] 
+// spectrum_ is [,,,nChan]
+// How annoying.
+{
+
+// Get slice and check dim
+
+  IPosition start, end;
+  setSlice(start, end, spec.shape(), spectrum_.shape(),
+            whichBeam, whichIF, False, False);
+
+// Get a reference to the Pol/Chan slice we are interested in
+
+  Array<Float> subArr = spectrum_(start,end);
+
+// Iterate through it and fill
+
+  ReadOnlyVectorIterator<Float> inIt(spec,0);
+  VectorIterator<Float> outIt(subArr,asap::ChanAxis);
+  while (!outIt.pastEnd()) {
+     outIt.vector() = inIt.vector();
+//
+     inIt.next();
+     outIt.next();
+  }
+
+  // unset flags for this spectrum, they might be set again by the
+  // setFlags method
+
+  Array<uChar> arr(flags_(start,end));
+  arr = uChar(0);
+//
+  return True;
+}
+
+Bool SDContainer::setFlags(const Matrix<uChar>& flags,
+			   uInt whichBeam, uInt whichIF,
+			   Bool hasXPol)
+//
+// flags is [nChan,nPol] 
+// flags_ is [nBeam,nIF,nPol,nChan]
+//
+// Note that there are no separate flags for the XY cross polarization so we make
+// them up from the X and Y which is all the silly code below.  This means
+// that nPol==2 on input but 4 on output
+//
+{
+  if (hasXPol) AlwaysAssert(nPol_==4,AipsError);
+
+// Get slice and check dim
+
+  IPosition start, end;
+  setSlice(start, end, flags.shape(), flags_.shape(),
+            whichBeam, whichIF, False, hasXPol);
+
+// Get a reference to the Pol/Chan slice we are interested in
+
+  Array<uChar> subArr = flags_(start,end);
+
+// Iterate through pol/chan plane  and fill
+
+  ReadOnlyVectorIterator<uChar> inIt(flags,0);
+  VectorIterator<uChar> outIt(subArr,asap::ChanAxis);
+//
+  Vector<uChar> maskPol0;
+  Vector<uChar> maskPol1;
+  Vector<uChar> maskPol01;
+  while (!outIt.pastEnd()) {
+     const IPosition& pos = outIt.pos();
+     if (pos(asap::PolAxis)<2) {
+        outIt.vector() = inIt.vector();
+//
+        if (hasXPol) {
+           if (pos(asap::PolAxis)==0) {
+              maskPol0 = inIt.vector();
+           } else if (pos(asap::PolAxis)==1) {
+              maskPol1 = inIt.vector();
+//
+              maskPol01.resize(maskPol0.nelements());
+              for (uInt i=0; i<maskPol01.nelements(); i++) maskPol01[i] = maskPol0[i]&maskPol1[i];
+           }
+        }
+        inIt.next();
+     } else if (pos(asap::PolAxis)==2) {
+        if (hasXPol) {
+           outIt.vector() = maskPol01;
+        } else {
+           outIt.vector() = inIt.vector();
+           inIt.next();
+        }
+
+     } else if (pos(asap::PolAxis)==3) { 
+        if (hasXPol) {
+           outIt.vector() = maskPol01;
+        } else {
+           outIt.vector() = inIt.vector();
+           inIt.next();
+        }
+     }
+     outIt.next();
+  }
+//
+  return True;
+}
+
+
+Bool SDContainer::setTsys(const Vector<Float>& tsys,
+			  uInt whichBeam, uInt whichIF,
+                          Bool hasXpol)
+//
+// TSys shape is [nPol]
+// Tsys does not depend upon channel but is replicated
+// for simplicity of use.
+// There is no Tsys measurement for the cross polarization
+// so I have set TSys for XY to sqrt(Tx*Ty)
+//
+{
+
+// Get slice and check dim
+
+  IPosition start, end;
+  setSlice(start, end, tsys.shape(), tsys_.shape(),
+            whichBeam, whichIF, True, hasXpol);
+
+// Get a reference to the Pol/Chan slice we are interested in
+
+  Array<Float> subArr = tsys_(start,end);
+
+// Iterate through it and fill
+
+  VectorIterator<Float> outIt(subArr,asap::ChanAxis);
+  uInt i=0;
+  while (!outIt.pastEnd()) {
+     const IPosition& pos = outIt.pos();
+//
+     if (pos(asap::PolAxis)<2) {
+       outIt.vector() = tsys(i++);
+     } else {
+       if (hasXpol) {
+          outIt.vector() = sqrt(tsys[0]*tsys[1]);
+       } else {
+          outIt.vector() = tsys(i++);
+       }
+     }
+//
+     outIt.next();
+  }
+}
+
+Array<Float> SDContainer::getSpectrum(uInt whichBeam, uInt whichIF)
+//
+// non-const function because of Array(start,end) slicer
+//
+// Input  [nBeam,nIF,nPol,nChan]
+// Output [nChan,nPol]
+//
+{
+
+// Get reference to slice and check dim
+
+  IPosition start, end;
+  setSlice(start, end, spectrum_.shape(), whichBeam, whichIF);
+//
+  Array<Float> dataIn = spectrum_(start,end);
+  Array<Float> dataOut(IPosition(2, nChan_, nPol_));
+//
+  ReadOnlyVectorIterator<Float> itIn(dataIn, asap::ChanAxis);
+  VectorIterator<Float> itOut(dataOut, 0);
+  while (!itOut.pastEnd()) {
+     itOut.vector() = itIn.vector();
+//
+     itIn.next();
+     itOut.next();
+  }
+//
+  return dataOut.copy(); 
+}
+
+Array<uChar> SDContainer::getFlags(uInt whichBeam, uInt whichIF)
+//
+// non-const function because of Array(start,end) slicer
+//
+// Input  [nBeam,nIF,nPol,nChan]
+// Output [nChan,nPol]
+//
+{
+
+// Get reference to slice and check dim
+
+  IPosition start, end;
+  setSlice(start, end, flags_.shape(), whichBeam, whichIF);
+//
+  Array<uChar> dataIn = flags_(start,end);
+  Array<uChar> dataOut(IPosition(2, nChan_, nPol_));
+//
+  ReadOnlyVectorIterator<uChar> itIn(dataIn, asap::ChanAxis);
+  VectorIterator<uChar> itOut(dataOut, 0);
+  while (!itOut.pastEnd()) {
+     itOut.vector() = itIn.vector();
+//
+     itIn.next();
+     itOut.next();
+  }
+//
+  return dataOut.copy();
+}
+
+Array<Float> SDContainer::getTsys(uInt whichBeam, uInt whichIF) 
+//
+// Input  [nBeam,nIF,nPol,nChan]
+// Output [nPol]   (drop channel dependency and select first value only)
+// 
+{
+// Get reference to slice and check dim
+
+  IPosition start, end;
+  setSlice(start, end, spectrum_.shape(), whichBeam, whichIF);
+//
+  Array<Float> dataIn = tsys_(start,end);
+  Vector<Float> dataOut(nPol_);
+//
+  ReadOnlyVectorIterator<Float> itIn(dataIn, asap::ChanAxis);
+  VectorIterator<Float> itOut(dataOut, 0); 
+  uInt i = 0;
+  while (!itIn.pastEnd()) {
+    dataOut[i++] = itIn.vector()[0];
+    itIn.next();
+  }
+//
+  return dataOut.copy();
+}
+
+
+
+Array<Double> SDContainer::getDirection(uInt whichBeam) const 
+//
+// Input [nBeam,2]
+// Output [nBeam]
+//
+{
+  Vector<Double> dataOut(2);
+  dataOut(0) = direction_(IPosition(2,whichBeam,0));
+  dataOut(1) = direction_(IPosition(2,whichBeam,1));
+  return dataOut.copy();
+}
+
+
+Bool SDContainer::setFrequencyMap(uInt freqID, uInt whichIF) {
+  freqidx_[whichIF] = freqID;
+  return True;
+}
+
+Bool SDContainer::putFreqMap(const Vector<uInt>& freqs) {
+  freqidx_.resize();
+  freqidx_ = freqs;
+  return True;
+}
+
+Bool SDContainer::setRestFrequencyMap(uInt freqID, uInt whichIF) {
+  restfreqidx_[whichIF] = freqID;
+  return True;
+}
+
+Bool SDContainer::putRestFreqMap(const Vector<uInt>& freqs) {
+  restfreqidx_.resize();
+  restfreqidx_ = freqs;
+  return True;
+}
+
+Bool SDContainer::setDirection(const Vector<Double>& point, uInt whichBeam) 
+//
+// Input [2]
+// Output [nBeam,2]
+//
+{
+  if (point.nelements() != 2) return False;
+//
+  Vector<Double> dataOut(2);
+  direction_(IPosition(2,whichBeam,0)) = point[0];
+  direction_(IPosition(2,whichBeam,1)) = point[1];
+  return True;
+}
+
+
+Bool SDContainer::putDirection(const Array<Double>& dir) {
+  direction_.resize();
+  direction_ = dir;
+  return True;
+}
+
+Bool SDContainer::putFitMap(const Array<Int>& arr) {
+  fitIDMap_.resize();
+  fitIDMap_ = arr;
+  return True;
+}
+
+void SDContainer::setSlice(IPosition& start, IPosition& end,
+			   const IPosition& shpIn, const IPosition& shpOut,
+			   uInt whichBeam, uInt whichIF, Bool tSys,
+			   Bool xPol) const
+//
+// tSYs
+//   shpIn [nPol]
+// else
+//   shpIn [nCHan,nPol]
+//
+// if xPol present, the output nPol = 4 but
+// the input spectra are nPol=2 (tSys) or nPol=2 (spectra)
+//
+{
+  AlwaysAssert(asap::nAxes==4,AipsError);
+  uInt pOff = 0;
+  if (xPol) pOff += 2;
+  if (tSys) {
+     AlwaysAssert(shpOut(asap::PolAxis)==shpIn(0)+pOff,AipsError);     // pol
+  } else {
+     AlwaysAssert(shpOut(asap::ChanAxis)==shpIn(0),AipsError);       // chan
+     AlwaysAssert(shpOut(asap::PolAxis)==shpIn(1)+pOff,AipsError);   // pol
+  }
+//
+  setSlice(start, end, shpOut, whichBeam, whichIF);
+}
+
+
+void SDContainer::setSlice(IPosition& start, IPosition& end, 
+			   const IPosition& shape, 
+			   uInt whichBeam, uInt whichIF) const
+{
+  AlwaysAssert(asap::nAxes==4,AipsError);
+  //
+  start.resize(asap::nAxes);
+  start = 0;
+  start(asap::BeamAxis) = whichBeam;
+  start(asap::IFAxis) = whichIF;
+//
+  end.resize(asap::nAxes);
+  end = shape-1;
+  end(asap::BeamAxis) = whichBeam;
+  end(asap::IFAxis) = whichIF;
+}
+
+
+uInt SDFrequencyTable::addFrequency(Double refPix, Double refVal, Double inc) 
+{
+  if (length() > 0) {
+    for (uInt i=0; i< length();i++) {
+      if (near(refVal,refVal_[i]) && 
+          near(refPix,refPix_[i]) && 
+          near(inc,increment_[i])) {
+         return i;
+      }
+    }
+  }
+
+// Not found - add it
+
+  nFreq_ += 1;
+  refPix_.resize(nFreq_,True);
+  refVal_.resize(nFreq_,True);
+  increment_.resize(nFreq_,True);
+  refPix_[nFreq_-1] = refPix;
+  refVal_[nFreq_-1] = refVal;
+  increment_[nFreq_-1] = inc;
+  return nFreq_-1;
+}
+
+uInt SDFrequencyTable::addRestFrequency(Double val)
+{
+  uInt nFreq = restFreqs_.nelements();
+  if (nFreq>0) {
+    for (uInt i=0; i<nFreq;i++) {
+      if (near(restFreqs_[i],val)) {
+         return i;
+      }
+    }
+  }
+
+// Not found - add it
+
+  nFreq += 1;
+  restFreqs_.resize(nFreq,True);
+  restFreqs_[nFreq-1] = val;
+  return nFreq-1;
+}
+
+
+void SDFrequencyTable::restFrequencies(Vector<Double>& rfs, 
+				       String& rfunit ) const
+{
+  rfs.resize(restFreqs_.nelements());
+  rfs = restFreqs_;
+  rfunit = restFreqUnit_;
+}
+
+// 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/asap1/src/SDContainer.h
===================================================================
--- tags/asap1/src/SDContainer.h	(revision 819)
+++ tags/asap1/src/SDContainer.h	(revision 819)
@@ -0,0 +1,266 @@
+//#---------------------------------------------------------------------------
+//# SDContainer.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 SDCONTAINER_H
+#define SDCONTAINER_H
+
+#include <vector>
+
+#include <casa/aips.h>
+#include <casa/BasicSL/String.h>
+#include <casa/Arrays/Array.h>
+#include <casa/Arrays/Vector.h>
+#include <casa/Containers/Block.h>
+#include <measures/Measures/MDirection.h>
+
+template<class T> class casa::Matrix;
+
+namespace asap {
+
+
+struct SDHeader {
+  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;
+  void print() const ;
+};
+
+class SDFrequencyTable {
+
+public:
+
+  SDFrequencyTable() : nFreq_(0) {;}
+  virtual ~SDFrequencyTable() {;}
+
+  // Add a new entry or match an existing one. Returns the index into
+  // the table
+  casa::uInt addFrequency(casa::Double refPix, casa::Double refVal,
+                          casa::Double inc);
+
+  casa::Int length() const { return nFreq_;}  // # of stored Frequencies
+  void setLength(casa::uInt length) {nFreq_ = length;}
+
+  // Get attributes
+  casa::Double referencePixel(casa::uInt which) const { return refPix_[which];}
+  casa::Double referenceValue(casa::uInt which) const { return refVal_[which];}
+  casa::Double increment(casa::uInt which) const { return increment_[which];}
+  casa::Float equinox() const { return equinox_; }
+  casa::String refFrame() const { return refFrame_; }
+  casa::String baseRefFrame() const { return baseRefFrame_; }
+  casa::String unit() const { return unit_; }
+
+  void restFrequencies(casa::Vector<casa::Double>& rfs,
+                       casa::String& rfunit ) const ;
+
+  // Set attributes
+  void setEquinox(casa::Float eq) { equinox_ = eq; }
+  void setRefFrame(const casa::String& reff) { refFrame_ = reff; }
+  void setBaseRefFrame(const casa::String& reff) { baseRefFrame_ = reff; }
+  void setUnit(const casa::String& un) { unit_= un; }
+
+  void deleteRestFrequencies() {restFreqs_.resize(0);}
+  casa::uInt addRestFrequency(casa::Double);
+  void setRestFrequencyUnit(const casa::String& theunit)
+  { restFreqUnit_ = theunit;}
+
+private:
+  casa::uInt nFreq_;
+  casa::Vector<casa::Double> refPix_;
+  casa::Vector<casa::Double> refVal_;           // Hz
+  casa::Vector<casa::Double> increment_;        // Hz
+  casa::Float equinox_;
+  casa::String unit_;
+  casa::String refFrame_;
+  casa::String baseRefFrame_;
+  casa::Vector<casa::Double> restFreqs_;        // Hz
+  casa::String restFreqUnit_;
+};
+
+
+class SDContainer {
+
+public:
+  SDContainer(casa::uInt nBeam, casa::uInt nIF, casa::uInt nPol,
+              casa::uInt nChan);
+  SDContainer(casa::IPosition shp);
+
+  virtual ~SDContainer();
+
+  casa::Bool resize(casa::IPosition shp);
+
+  casa::Bool setSpectrum(const casa::Matrix<casa::Float>& spec,
+                   casa::uInt whichBeam, casa::uInt whichIF);
+  casa::Bool setSpectrum(const casa::Matrix<casa::Float>& spec,
+                         const casa::Vector<casa::Complex>& cSpec,
+                         casa::uInt whichBeam, casa::uInt whichIF);
+  casa::Bool putSpectrum(const casa::Array<casa::Float>& spec);
+
+  casa::Bool setFlags(const casa::Matrix<casa::uChar>& flgs,
+                      casa::uInt whichBeam, casa::uInt whichIF,
+                      casa::Bool hasXPol=casa::False);
+  casa::Bool putFlags(const casa::Array<casa::uChar>& spec);
+
+  casa::Bool setTsys(const casa::Vector<casa::Float>& ts,
+               casa::uInt whichBeam, casa::uInt whichIF,
+               casa::Bool hasXpol);
+  casa::Bool putTsys(const casa::Array<casa::Float>& spec);
+
+  casa::Bool setDirection(const casa::Vector<casa::Double>& point,
+                          casa::uInt whichBeam);
+  casa::Bool putDirection(const casa::Array<casa::Double>& dir);
+
+  casa::Bool setFrequencyMap(casa::uInt freqslot, casa::uInt whichIF);
+  casa::Bool putFreqMap(const casa::Vector<casa::uInt>& freqs);
+
+  casa::Bool setRestFrequencyMap(casa::uInt freqslot, casa::uInt whichIF);
+  casa::Bool putRestFreqMap(const casa::Vector<casa::uInt>& freqs);
+
+  casa::Array<casa::Float> getSpectrum(casa::uInt whichBeam,
+                                       casa::uInt whichIF);
+  casa::Array<casa::uChar> getFlags(casa::uInt whichBeam,
+                                    casa::uInt whichIF);
+  casa::Array<casa::Float> getTsys(casa::uInt whichBeam,
+                                   casa::uInt whichIF);
+  casa::Array<casa::Double> getDirection(casa::uInt whichBeam) const;
+
+  const casa::Array<casa::Float>& getSpectrum() const { return spectrum_; }
+  const casa::Array<casa::uChar>& getFlags() const { return flags_; }
+  const casa::Array<casa::Float>& getTsys() const { return tsys_; }
+  const casa::Array<casa::Double>& getDirection() const { return direction_; }
+
+  const casa::Vector<casa::uInt>& getFreqMap() const { return freqidx_; }
+  const casa::Vector<casa::uInt>& getRestFreqMap() const
+  { return restfreqidx_; }
+
+  casa::Bool putFitMap(const casa::Array<casa::Int>& arr);
+
+  const casa::Array<casa::Int>& getFitMap() const { return fitIDMap_; }
+
+
+  casa::Double timestamp;
+  //Double bandwidth;
+  casa::String sourcename;
+  casa::String fieldname;
+  casa::Double interval;
+  casa::Int scanid;
+  casa::Vector<casa::Float> tcal;
+  casa::String tcaltime;
+  casa::Float azimuth;
+  casa::Float elevation;
+  casa::Float parangle;
+  casa::Int refbeam;
+
+private:
+  casa::uInt nBeam_,nIF_,nPol_,nChan_;
+
+  // (nBeam,nIF,nPol,nChannel)
+  casa::Array<casa::Float>    spectrum_;
+  casa::Array<casa::uChar>    flags_;
+
+  // (nBeam,nIF,nPol,[nChannel]) Tsys is not really a function of
+  // channel, but this makes it easier to work with at the expense of
+  // a little memory
+  casa::Array<casa::Float>    tsys_;
+  casa::Array<casa::Float>    tcal_;
+
+  //(nIF) indx into "global" frequency table
+  casa::Vector<casa::uInt>    freqidx_;
+
+ // (nIF) indx into "global" rest frequency table
+  casa::Vector<casa::uInt>    restfreqidx_;
+
+  //(nBeam,2) maybe use Measures here...
+  casa::Array<casa::Double>   direction_;
+  casa::Array<casa::Int> fitIDMap_;
+
+  void setSlice(casa::IPosition& start, casa::IPosition& end,
+                const casa::IPosition& shpIn, const casa::IPosition& shpOut,
+                casa::uInt whichBeam, casa::uInt whichIF, casa::Bool checkPol,
+                casa::Bool xPol) const;
+  void setSlice(casa::IPosition& start, casa::IPosition& end,
+                const casa::IPosition& shape,
+                casa::uInt whichBeam, casa::uInt whichIF) 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/asap1/src/SDDefs.h
===================================================================
--- tags/asap1/src/SDDefs.h	(revision 819)
+++ tags/asap1/src/SDDefs.h	(revision 819)
@@ -0,0 +1,54 @@
+//#---------------------------------------------------------------------------
+//# SDDefs.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 SDDEFS_H
+#define SDDEFS_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};
+}
+
+#endif
Index: tags/asap1/src/SDFITSImageWriter.cc
===================================================================
--- tags/asap1/src/SDFITSImageWriter.cc	(revision 819)
+++ tags/asap1/src/SDFITSImageWriter.cc	(revision 819)
@@ -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 "SDDefs.h"
+#include "SDContainer.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
+
+   SDHeader header = sdTable.getSDHeader();
+   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/asap1/src/SDFITSImageWriter.h
===================================================================
--- tags/asap1/src/SDFITSImageWriter.h	(revision 819)
+++ tags/asap1/src/SDFITSImageWriter.h	(revision 819)
@@ -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 "SDLog.h"
+
+namespace asap {
+
+class SDFITSImageWriter : public SDLog {
+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/asap1/src/SDFitTable.cc
===================================================================
--- tags/asap1/src/SDFitTable.cc	(revision 819)
+++ tags/asap1/src/SDFitTable.cc	(revision 819)
@@ -0,0 +1,158 @@
+//#---------------------------------------------------------------------------
+//# SDFitTable.h: A wrapper for fit parameters
+//#---------------------------------------------------------------------------
+//# 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 "MathUtils.h"
+#include "SDFitTable.h"
+
+using namespace casa;
+using namespace asap;
+
+SDFitTable::SDFitTable(const SDFitTable& other) {  
+  if (other.length() > 0) {
+    this->nfits_ = other.nfits_;
+    this->pars_ = other.pars_;
+    this->mask_ = other.mask_;
+    this->funcs_ = other.funcs_;
+    this->comps_ = other.comps_;
+    this->frameinfo_ = other.frameinfo_;
+  } else {
+    this->nfits_ = 0;
+  }
+}
+
+
+void SDFitTable::addSTLFit(const std::vector<double>& p, 
+		      const std::vector<bool>& m,
+		      const std::vector<std::string>& f, 
+		      const std::vector<Int>& c,
+		      const std::vector<std::string>& fi)
+{
+  pars_.push_back(p);
+  mask_.push_back(m);
+  funcs_.push_back(f);
+  comps_.push_back(c);
+  frameinfo_.push_back(fi);
+  nfits_++;
+}
+
+void SDFitTable::addFit(const Vector<Double>& p, 
+		   const Vector<Bool>& m,
+		   const Vector<String>& f, 
+		   const Vector<Int>& c,
+		   const Vector<String>& fi)
+{
+  std::vector<double> p1;
+  p.tovector(p1);
+  pars_.push_back(p1);
+  std::vector<bool> m1;
+  m.tovector(m1);
+  mask_.push_back(m1);
+  std::vector<string> f1;
+  f1 = mathutil::tovectorstring(f);
+  funcs_.push_back(f1);
+  std::vector<int> c1;
+  c.tovector(c1);
+  comps_.push_back(c1);
+  std::vector<string> fi1;
+  fi1 = mathutil::tovectorstring(fi);
+  frameinfo_.push_back(fi1);
+  nfits_++;
+}
+
+std::vector<double> SDFitTable::getSTLParameters(int which) const
+{
+  if (which >= nfits_) 
+    return std::vector<double>();  
+  return pars_[which];
+}
+
+Vector<Double> SDFitTable::getParameters(uInt which) const{
+  if (int(which) >= nfits_) 
+    return Vector<Double>(std::vector<double>());  
+  return Vector<Double>(pars_[which]);
+}
+
+std::vector<bool> SDFitTable::getSTLParameterMask(int which) const
+{
+  if (which >= nfits_) 
+    return  std::vector<bool>();
+  return mask_[which];
+}
+
+Vector<Bool> SDFitTable::getParameterMask(uInt which) const
+{
+  if (which >= nfits_)
+    return  Vector<Bool>(std::vector<bool>());  
+  return Vector<Bool>(mask_[which]);
+}
+
+std::vector<std::string> SDFitTable::getSTLFunctions(int which) const
+{
+  if (which >= nfits_) 
+    return std::vector<std::string>();  
+  return funcs_[which];
+}
+
+Vector<String> SDFitTable::getFunctions(uInt which) const
+{
+  if (int(which) >= nfits_) 
+    return mathutil::toVectorString(std::vector<std::string>());
+  return mathutil::toVectorString(funcs_[which]);
+}
+
+std::vector<int> SDFitTable::getSTLComponents(int which) const
+{
+  if (which >= nfits_) 
+    return std::vector<int>();  
+  return comps_[which];
+}
+
+Vector<Int> SDFitTable::getComponents(uInt which) const
+{
+  if (int(which) >= nfits_) 
+    return Vector<Int>(std::vector<int>());  
+  return Vector<Int>(comps_[which]);
+}
+
+std::vector<std::string> SDFitTable::getSTLFrameInfo(int which) const
+{
+  if (which >= nfits_) 
+    return std::vector<std::string>();  
+  return frameinfo_[which];
+}
+
+Vector<String> SDFitTable::getFrameInfo(uInt which) const
+{
+  if (int(which) >= nfits_) 
+    return mathutil::toVectorString(std::vector<std::string>());
+  return mathutil::toVectorString(frameinfo_[which]);
+}
+
Index: tags/asap1/src/SDFitTable.h
===================================================================
--- tags/asap1/src/SDFitTable.h	(revision 819)
+++ tags/asap1/src/SDFitTable.h	(revision 819)
@@ -0,0 +1,88 @@
+//#---------------------------------------------------------------------------
+//# SDFitTable.h: A wrapper for fit parameters
+//#---------------------------------------------------------------------------
+//# 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 SDFITTABLE_H
+#define SDFITTABLE_H
+
+#include <casa/aips.h>
+#include <casa/Arrays/Vector.h>
+#include <casa/BasicSL/String.h>
+#include <vector>
+#include <string>
+
+namespace asap {
+
+class SDFitTable {
+public:
+  SDFitTable() : nfits_(0) {;};
+  ~SDFitTable() {;};
+  SDFitTable(const SDFitTable& other);
+
+  void addFit(const casa::Vector<casa::Double>& p, 
+	      const casa::Vector<casa::Bool>& m,
+	      const casa::Vector<casa::String>& f, 
+	      const casa::Vector<casa::Int>& c,
+	      const casa::Vector<casa::String>& fi);
+  
+  void addSTLFit(const std::vector<double>& p, 
+		 const std::vector<bool>& m,
+		 const std::vector<std::string>& f, 
+		 const std::vector<int>& c,
+		 const std::vector<std::string>& fi);
+  
+  int STLlength() const { return nfits_; };
+  
+  casa::uInt length() const { return casa::uInt(nfits_); };
+  
+  std::vector<double> getSTLParameters(int which) const;
+  std::vector<bool> getSTLParameterMask(int which) const;
+  std::vector<std::string> getSTLFunctions(int which) const;
+  std::vector<int> getSTLComponents(int which) const;
+  std::vector<std::string> getSTLFrameInfo(int which) const;
+
+  casa::Vector<casa::Double> getParameters(casa::uInt which) const;
+  casa::Vector<casa::Bool> getParameterMask(casa::uInt which) const;
+  casa::Vector<casa::String> getFunctions(casa::uInt which) const;
+  casa::Vector<casa::Int> getComponents(casa::uInt which) const;
+  casa::Vector<casa::String> getFrameInfo(casa::uInt which) const;
+
+
+private:
+  int nfits_;
+  std::vector<std::vector<double> > pars_;
+  std::vector<std::vector<bool> > mask_;
+  std::vector<std::vector<std::string> > funcs_;
+  std::vector<std::vector<int> > comps_;
+  std::vector<std::vector<std::string> > frameinfo_;
+};
+
+}//
+
+#endif
Index: tags/asap1/src/SDFitter.cc
===================================================================
--- tags/asap1/src/SDFitter.cc	(revision 819)
+++ tags/asap1/src/SDFitter.cc	(revision 819)
@@ -0,0 +1,331 @@
+//#---------------------------------------------------------------------------
+//# SDFitter.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 "SDFitter.h"
+using namespace asap;
+using namespace casa;
+
+SDFitter::SDFitter()
+{
+}
+
+SDFitter::~SDFitter()
+{
+  reset();
+}
+
+void SDFitter::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 SDFitter::reset()
+{
+  clear();
+  x_.resize();
+  y_.resize();
+  m_.resize();
+}
+
+
+bool SDFitter::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> SDFitter::getEstimate() const
+{
+  if (estimate_.nelements() == 0)
+    throw (AipsError("No estimate set."));
+  std::vector<float> stlout;
+  estimate_.tovector(stlout);
+  return stlout;
+}
+
+
+bool SDFitter::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 SDFitter::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> SDFitter::getResidual() const
+{
+    if (residual_.nelements() == 0)
+        throw (AipsError("Function not yet fitted."));
+    std::vector<float> stlout;
+    residual_.tovector(stlout);
+    return stlout;
+}
+
+std::vector<float> SDFitter::getFit() const
+{
+    Vector<Float> out = thefit_;
+    std::vector<float> stlout;
+    out.tovector(stlout);
+    return stlout;
+
+}
+
+std::vector<float> SDFitter::getErrors() const
+{
+    Vector<Float> out = error_;
+    std::vector<float> stlout;
+    out.tovector(stlout);
+    return stlout;
+}
+
+bool SDFitter::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 SDFitter::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> SDFitter::getParameters() const {
+    Vector<Float> out = parameters_;
+    std::vector<float> stlout;
+    out.tovector(stlout);
+    return stlout;
+}
+
+std::vector<bool> SDFitter::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 SDFitter::getChisquared() const {
+    return chisquared_;
+}
+
+bool SDFitter::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_);
+  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> SDFitter::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/asap1/src/SDFitter.h
===================================================================
--- tags/asap1/src/SDFitter.h	(revision 819)
+++ tags/asap1/src/SDFitter.h	(revision 819)
@@ -0,0 +1,88 @@
+//#---------------------------------------------------------------------------
+//# SDFitter.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 SDFITTER_H
+#define SDFITTER_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 SDFitter {
+public:
+  SDFitter();
+  virtual ~SDFitter();
+  // 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/asap1/src/SDLineFinder.cc
===================================================================
--- tags/asap1/src/SDLineFinder.cc	(revision 819)
+++ tags/asap1/src/SDLineFinder.cc	(revision 819)
@@ -0,0 +1,1077 @@
+//#---------------------------------------------------------------------------
+//# SDLineFinder.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 "SDLineFinder.h"
+#include "SDFitter.h"
+
+// STL
+#include <functional>
+#include <algorithm>
+#include <iostream>
+#include <fstream>
+
+using namespace asap;
+using namespace casa;
+using namespace std;
+using namespace boost::python;
+
+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)
+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);
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+
+} // 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;
+}
+
+
+// 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 (detect) {
+           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
+	   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;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// SDLineFinder  -  a class for automated spectral line search
+//
+//
+
+SDLineFinder::SDLineFinder() 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 SDLineFinder::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;
+}
+
+SDLineFinder::~SDLineFinder() throw(AipsError) {}
+
+// set scan to work with (in_scan parameter), associated mask (in_mask
+// parameter) and the edge channel rejection (in_edge parameter)
+//   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   
+void SDLineFinder::setScan(const SDMemTableWrapper &in_scan,
+               const std::vector<bool> &in_mask,
+	       const boost::python::tuple &in_edge) throw(AipsError)
+{
+  try {
+       scan=in_scan.getCP();
+       AlwaysAssert(!scan.null(),AipsError);
+
+       mask=in_mask;
+       if (mask.nelements()!=scan->nChan())
+           throw AipsError("SDLineFinder::setScan - in_scan and in_mask have different"
+	                   "number of spectral channels.");
+
+       // number of elements in the in_edge tuple
+       int n=extract<int>(in_edge.attr("__len__")());
+       if (n>2 || n<0)
+           throw AipsError("SDLineFinder::setScan - the length of the in_edge parameter"
+	                   "should not exceed 2");
+       if (!n) {
+           // all spectrum, no rejection
+           edge.first=0;
+	   edge.second=scan->nChan();
+       } else {
+           edge.first=extract<int>(in_edge.attr("__getitem__")(0));
+	   if (edge.first<0)
+	       throw AipsError("SDLineFinder::setScan - the in_edge parameter has a negative"
+	                        "number of channels to drop");
+           if (edge.first>=scan->nChan())
+	       throw AipsError("SDLineFinder::setScan - all channels are rejected by the in_edge parameter");
+           if (n==2) {
+	       edge.second=extract<int>(in_edge.attr("__getitem__")(1));
+	       if (edge.second<0)
+  	           throw AipsError("SDLineFinder::setScan - the in_edge parameter has a negative"
+	                           "number of channels to drop");
+               edge.second=scan->nChan()-edge.second;
+	   } else edge.second=scan->nChan()-edge.first;
+           if (edge.second<0 || (edge.first>=edge.second))
+	       throw AipsError("SDLineFinder::setScan - all channels are rejected by the in_edge parameter");
+       }       
+  }
+  catch(const AipsError &ae) {
+       // setScan is unsuccessfull, reset scan/mask/edge
+       scan=CountedConstPtr<SDMemTable>(); // null pointer
+       mask.resize(0);
+       edge=pair<int,int>(0,0);
+       throw;
+  }
+}
+
+// search for spectral lines. Number of lines found is returned
+int SDLineFinder::findLines(const casa::uInt &whichRow) throw(casa::AipsError)
+{ 
+  const int minboxnchan=4;
+  if (scan.null())
+      throw AipsError("SDLineFinder::findLines - a scan should be set first,"
+                      " use set_scan");
+  DebugAssert(mask.nelements()==scan->nChan(), AipsError);
+  int max_box_nchan=int(scan->nChan()*box_size); // number of channels in running
+                                                 // box
+  if (max_box_nchan<2)
+      throw AipsError("SDLineFinder::findLines - box_size is too small");
+
+  scan->getSpectrum(spectrum, 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 SDFitter class. This action is required before
+// reducing the spectral resolution if the baseline shape is bad
+void SDLineFinder::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
+  SDFitter sdf;
+  std::vector<float> absc(spectrum.nelements());
+  for (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 SDLineFinder::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> SDLineFinder::getMask(bool invert)
+                                        const throw(casa::AipsError)
+{
+  try {
+       if (scan.null())
+           throw AipsError("SDLineFinder::getMask - a scan should be set first,"
+                      " use set_scan followed by find_lines");
+       DebugAssert(mask.nelements()==scan->nChan(), AipsError);
+       /*
+       if (!lines.size())
+           throw AipsError("SDLineFinder::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<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("SDLineFinder::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> SDLineFinder::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("SDLineFinder::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> SDLineFinder::getLineRangesInChannels()
+                                   const throw(casa::AipsError)
+{
+  try {
+       if (scan.null())
+           throw AipsError("SDLineFinder::getLineRangesInChannels - a scan should be set first,"
+                      " use set_scan followed by find_lines");
+       DebugAssert(mask.nelements()==scan->nChan(), AipsError);
+       
+       if (!lines.size())
+           throw AipsError("SDLineFinder::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("SDLineFinder::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 SDLineFinder::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("SDLineFinder::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/asap1/src/SDLineFinder.h
===================================================================
--- tags/asap1/src/SDLineFinder.h	(revision 819)
+++ tags/asap1/src/SDLineFinder.h	(revision 819)
@@ -0,0 +1,253 @@
+//#---------------------------------------------------------------------------
+//# SDLineFinder.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 SDLINEFINDER_H
+#define SDLINEFINDER_H
+
+// STL
+#include <vector>
+#include <list>
+#include <utility>
+#include <exception>
+
+// boost
+#include <boost/python.hpp>
+
+// 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 "SDMemTableWrapper.h"
+#include "SDMemTable.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();
+   }; 
+   
+   
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// SDLineFinder  -  a class for automated spectral line search
+//
+//
+
+struct SDLineFinder : protected LFLineListOperations {
+   SDLineFinder() throw();
+   virtual ~SDLineFinder() 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), associated mask (in_mask
+   // parameter) and the edge channel rejection (in_edge parameter)
+   //   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   
+   void setScan(const SDMemTableWrapper &in_scan,
+                const std::vector<bool> &in_mask,
+		const boost::python::tuple &in_edge) throw(casa::AipsError);
+
+   // search for spectral lines for a row specified by whichRow and
+   // Beam/IF/Pol specified by current cursor set for the scantable
+   // Number of lines found is returned   
+   int findLines(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 SDFitter 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<SDMemTable> 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 SDLINEFINDER_H
Index: tags/asap1/src/SDLog.cc
===================================================================
--- tags/asap1/src/SDLog.cc	(revision 819)
+++ tags/asap1/src/SDLog.cc	(revision 819)
@@ -0,0 +1,36 @@
+#include "SDLog.h"
+
+using namespace asap;
+
+std::string SDLog::log_ = std::string("");
+
+SDLog::SDLog() 
+{ 
+  enableLog();
+}
+
+SDLog::SDLog(bool enabled)
+{ 
+  enabled_ = enabled;
+}
+
+void SDLog::pushLog(const std::string& s) const
+{ 
+  if (enabled_) {
+    log_+=s;log_+="\n";
+  }; 
+}
+std::string SDLog::popLog() const
+{
+  std::string out; 
+  if (enabled_) out=log_;log_="";
+  return out;
+}
+void SDLog::enableLog()
+{ 
+  enabled_ = true;
+}
+void SDLog::disableLog()
+{
+  enabled_ = false;
+}
Index: tags/asap1/src/SDLog.h
===================================================================
--- tags/asap1/src/SDLog.h	(revision 819)
+++ tags/asap1/src/SDLog.h	(revision 819)
@@ -0,0 +1,26 @@
+#ifndef SDLOG_H
+#define SDLOG_H
+
+#include <string>
+#include <iostream>
+
+namespace asap {
+
+class SDLog {
+public:
+  SDLog();
+  SDLog(bool enabled);
+  void pushLog(const std::string& s) const;
+  //void pushLog(const char* cs);
+  std::string popLog() const;
+  virtual void enableLog();
+  virtual void disableLog();
+private:
+  static std::string log_;
+  bool enabled_;
+};
+
+}
+
+#endif
+
Index: tags/asap1/src/SDMath.cc
===================================================================
--- tags/asap1/src/SDMath.cc	(revision 819)
+++ tags/asap1/src/SDMath.cc	(revision 819)
@@ -0,0 +1,2244 @@
+//#---------------------------------------------------------------------------
+//# SDMath.cc: A collection of single dish mathematical 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 <vector>
+
+
+#include <casa/aips.h>
+#include <casa/iostream.h>
+#include <casa/sstream.h>
+#include <casa/iomanip.h>
+#include <casa/BasicSL/String.h>
+#include <casa/Arrays/IPosition.h>
+#include <casa/Arrays/Array.h>
+#include <casa/Arrays/ArrayIter.h>
+#include <casa/Arrays/VectorIter.h>
+#include <casa/Arrays/ArrayMath.h>
+#include <casa/Arrays/ArrayLogical.h>
+#include <casa/Arrays/MaskedArray.h>
+#include <casa/Arrays/MaskArrMath.h>
+#include <casa/Arrays/MaskArrLogi.h>
+#include <casa/Arrays/Matrix.h>
+#include <casa/BasicMath/Math.h>
+#include <casa/Exceptions.h>
+#include <casa/Quanta/Quantum.h>
+#include <casa/Quanta/Unit.h>
+#include <casa/Quanta/MVEpoch.h>
+#include <casa/Quanta/MVTime.h>
+#include <casa/Utilities/Assert.h>
+
+#include <coordinates/Coordinates/SpectralCoordinate.h>
+#include <coordinates/Coordinates/CoordinateSystem.h>
+#include <coordinates/Coordinates/CoordinateUtil.h>
+#include <coordinates/Coordinates/FrequencyAligner.h>
+
+#include <lattices/Lattices/LatticeUtilities.h>
+#include <lattices/Lattices/RebinLattice.h>
+
+#include <measures/Measures/MEpoch.h>
+#include <measures/Measures/MDirection.h>
+#include <measures/Measures/MPosition.h>
+
+#include <scimath/Mathematics/VectorKernel.h>
+#include <scimath/Mathematics/Convolver.h>
+#include <scimath/Mathematics/InterpolateArray1D.h>
+#include <scimath/Functionals/Polynomial.h>
+
+#include <tables/Tables/Table.h>
+#include <tables/Tables/ScalarColumn.h>
+#include <tables/Tables/ArrayColumn.h>
+#include <tables/Tables/ReadAsciiTable.h>
+
+#include "MathUtils.h"
+#include "SDDefs.h"
+#include "SDAttr.h"
+#include "SDContainer.h"
+#include "SDMemTable.h"
+
+#include "SDMath.h"
+#include "SDPol.h"
+
+using namespace casa;
+using namespace asap;
+
+
+SDMath::SDMath()
+{
+}
+
+SDMath::SDMath(const SDMath& other)
+{
+
+// No state
+
+}
+
+SDMath& SDMath::operator=(const SDMath& other)
+{
+  if (this != &other) {
+// No state
+  }
+  return *this;
+}
+
+SDMath::~SDMath()
+{;}
+
+
+SDMemTable* SDMath::frequencyAlignment(const SDMemTable& in,
+                                       const String& refTime,
+                                       const String& method,
+                                       Bool perFreqID)
+{
+  // Get frame info from Table
+   std::vector<std::string> info = in.getCoordInfo();
+
+   // Parse frequency system
+   String systemStr(info[1]);
+   String baseSystemStr(info[3]);
+   if (baseSystemStr==systemStr) {
+     throw(AipsError("You have not set a frequency frame different from the initial - use function set_freqframe"));
+   }
+
+   MFrequency::Types freqSystem;
+   MFrequency::getType(freqSystem, systemStr);
+
+   return frequencyAlign(in, freqSystem, refTime, method, perFreqID);
+}
+
+
+
+CountedPtr<SDMemTable>
+SDMath::average(const std::vector<CountedPtr<SDMemTable> >& in,
+                const Vector<Bool>& mask, Bool scanAv,
+                const String& weightStr, Bool alignFreq)
+// Weighted averaging of spectra from one or more Tables.
+{
+  // Convert weight type
+  WeightType wtType = NONE;
+  convertWeightString(wtType, weightStr, True);
+
+  // Create output Table by cloning from the first table
+  SDMemTable* pTabOut = new SDMemTable(*in[0],True);
+  if (in.size() > 1) {
+    for (uInt i=1; i < in.size(); ++i) {
+      pTabOut->appendToHistoryTable(in[i]->getHistoryTable());
+    }
+  }
+  // Setup
+  IPosition shp = in[0]->rowAsMaskedArray(0).shape();      // Must not change
+  Array<Float> arr(shp);
+  Array<Bool> barr(shp);
+  const Bool useMask = (mask.nelements() == shp(asap::ChanAxis));
+
+  // Columns from Tables
+  ROArrayColumn<Float> tSysCol;
+  ROScalarColumn<Double> mjdCol;
+  ROScalarColumn<String> srcNameCol;
+  ROScalarColumn<Double> intCol;
+  ROArrayColumn<uInt> fqIDCol;
+  ROScalarColumn<Int> scanIDCol;
+
+  // Create accumulation MaskedArray. We accumulate for each
+  // channel,if,pol,beam Note that the mask of the accumulation array
+  // will ALWAYS remain ALL True.  The MA is only used so that when
+  // data which is masked Bad is added to it, that data does not
+  // contribute.
+
+  Array<Float> zero(shp);
+  zero=0.0;
+  Array<Bool> good(shp);
+  good = True;
+  MaskedArray<Float> sum(zero,good);
+
+  // Counter arrays
+  Array<Float> nPts(shp);             // Number of points
+  nPts = 0.0;
+  Array<Float> nInc(shp);             // Increment
+  nInc = 1.0;
+
+  // Create accumulation Array for variance. We accumulate for each
+  // if,pol,beam, but average over channel.  So we need a shape with
+  // one less axis dropping channels.
+  const uInt nAxesSub = shp.nelements() - 1;
+  IPosition shp2(nAxesSub);
+  for (uInt i=0,j=0; i<(nAxesSub+1); i++) {
+     if (i!=asap::ChanAxis) {
+       shp2(j) = shp(i);
+       j++;
+     }
+  }
+  Array<Float> sumSq(shp2);
+  sumSq = 0.0;
+  IPosition pos2(nAxesSub,0);                        // For indexing
+
+  // Time-related accumulators
+  Double time;
+  Double timeSum = 0.0;
+  Double intSum = 0.0;
+  Double interval = 0.0;
+
+  // To get the right shape for the Tsys accumulator we need to access
+  // a column from the first table.  The shape of this array must not
+  // change.  Note however that since the TSysSqSum array is used in a
+  // normalization process, and that I ignore the channel axis
+  // replication of values for now, it loses a dimension
+
+  Array<Float> tSysSum, tSysSqSum;
+  {
+    const Table& tabIn = in[0]->table();
+    tSysCol.attach(tabIn,"TSYS");
+    tSysSum.resize(tSysCol.shape(0));
+    tSysSqSum.resize(shp2);
+  }
+  tSysSum = 0.0;
+  tSysSqSum = 0.0;
+  Array<Float> tSys;
+
+  // Scan and row tracking
+  Int oldScanID = 0;
+  Int outScanID = 0;
+  Int scanID = 0;
+  Int rowStart = 0;
+  Int nAccum = 0;
+  Int tableStart = 0;
+
+  // Source and FreqID
+  String sourceName, oldSourceName, sourceNameStart;
+  Vector<uInt> freqID, freqIDStart, oldFreqID;
+
+  // Loop over tables
+  Float fac = 1.0;
+  const uInt nTables = in.size();
+  for (uInt iTab=0; iTab<nTables; iTab++) {
+
+    // Should check that the frequency tables don't change if doing
+    // FreqAlignment
+
+    // Attach columns to Table
+     const Table& tabIn = in[iTab]->table();
+     tSysCol.attach(tabIn, "TSYS");
+     mjdCol.attach(tabIn, "TIME");
+     srcNameCol.attach(tabIn, "SRCNAME");
+     intCol.attach(tabIn, "INTERVAL");
+     fqIDCol.attach(tabIn, "FREQID");
+     scanIDCol.attach(tabIn, "SCANID");
+
+     // Loop over rows in Table
+     const uInt nRows = in[iTab]->nRow();
+     for (uInt iRow=0; iRow<nRows; iRow++) {
+       // Check conformance
+        IPosition shp2 = in[iTab]->rowAsMaskedArray(iRow).shape();
+        if (!shp.isEqual(shp2)) {
+          delete pTabOut;
+           throw (AipsError("Shapes for all rows must be the same"));
+        }
+
+        // If we are not doing scan averages, make checks for source
+        // and frequency setup and warn if averaging across them
+        scanIDCol.getScalar(iRow, scanID);
+
+        // Get quantities from columns
+        srcNameCol.getScalar(iRow, sourceName);
+        mjdCol.get(iRow, time);
+        tSysCol.get(iRow, tSys);
+        intCol.get(iRow, interval);
+        fqIDCol.get(iRow, freqID);
+
+        // Initialize first source and freqID
+        if (iRow==0 && iTab==0) {
+          sourceNameStart = sourceName;
+          freqIDStart = freqID;
+        }
+
+        // If we are doing scan averages, see if we are at the end of
+        // an accumulation period (scan).  We must check soutce names
+        // too, since we might have two tables with one scan each but
+        // different source names; we shouldn't average different
+        // sources together
+        if (scanAv && ( (scanID != oldScanID)  ||
+                        (iRow==0 && iTab>0 && sourceName!=oldSourceName))) {
+
+          // Normalize data in 'sum' accumulation array according to
+          // weighting scheme
+           normalize(sum, sumSq, tSysSqSum, nPts, intSum, wtType,
+                     asap::ChanAxis, nAxesSub);
+
+           // Get ScanContainer for the first row of this averaged Scan
+           SDContainer scOut = in[iTab]->getSDContainer(rowStart);
+
+           // Fill scan container. The source and freqID come from the
+           // first row of the first table that went into this average
+           // ( should be the same for all rows in the scan average)
+
+           Float nR(nAccum);
+           fillSDC(scOut, sum.getMask(), sum.getArray(), tSysSum/nR, outScanID,
+                   timeSum/nR, intSum, sourceNameStart, freqIDStart);
+
+           // Write container out to Table
+           pTabOut->putSDContainer(scOut);
+
+           // Reset accumulators
+           sum = 0.0;
+           sumSq = 0.0;
+           nAccum = 0;
+
+           tSysSum =0.0;
+           tSysSqSum =0.0;
+           timeSum = 0.0;
+           intSum = 0.0;
+           nPts = 0.0;
+
+           // Increment
+           rowStart = iRow;              // First row for next accumulation
+           tableStart = iTab;            // First table for next accumulation
+           sourceNameStart = sourceName; // First source name for next
+                                         // accumulation
+           freqIDStart = freqID;         // First FreqID for next accumulation
+
+           oldScanID = scanID;
+           outScanID += 1;               // Scan ID for next
+                                         // accumulation period
+        }
+
+        // Accumulate
+        accumulate(timeSum, intSum, nAccum, sum, sumSq, nPts,
+                   tSysSum, tSysSqSum, tSys,
+                   nInc, mask, time, interval, in, iTab, iRow, asap::ChanAxis,
+                   nAxesSub, useMask, wtType);
+        oldSourceName = sourceName;
+        oldFreqID = freqID;
+     }
+  }
+
+  // OK at this point we have accumulation data which is either
+  //   - accumulated from all tables into one row
+  // or
+  //   - accumulated from the last scan average
+  //
+  // Normalize data in 'sum' accumulation array according to weighting
+  // scheme
+
+  normalize(sum, sumSq, tSysSqSum, nPts, intSum, wtType,
+            asap::ChanAxis, nAxesSub);
+
+  // Create and fill container.  The container we clone will be from
+  // the last Table and the first row that went into the current
+  // accumulation.  It probably doesn't matter that much really...
+  Float nR(nAccum);
+  SDContainer scOut = in[tableStart]->getSDContainer(rowStart);
+  fillSDC(scOut, sum.getMask(), sum.getArray(), tSysSum/nR, outScanID,
+          timeSum/nR, intSum, sourceNameStart, freqIDStart);
+  pTabOut->putSDContainer(scOut);
+  pTabOut->resetCursor();
+
+  return CountedPtr<SDMemTable>(pTabOut);
+}
+
+
+
+CountedPtr<SDMemTable>
+SDMath::binaryOperate(const CountedPtr<SDMemTable>& left,
+                      const CountedPtr<SDMemTable>& right,
+                      const String& op, Bool preserve, Bool doTSys)
+{
+
+  // Check operator
+  String op2(op);
+  op2.upcase();
+  uInt what = 0;
+  if (op2=="ADD") {
+     what = 0;
+  } else if (op2=="SUB") {
+     what = 1;
+  } else if (op2=="MUL") {
+     what = 2;
+  } else if (op2=="DIV") {
+     what = 3;
+  } else if (op2=="QUOTIENT") {
+     what = 4;
+     doTSys = True;
+  } else {
+    throw( AipsError("Unrecognized operation"));
+  }
+
+  // Check rows
+  const uInt nRowLeft = left->nRow();
+  const uInt nRowRight = right->nRow();
+  Bool ok = (nRowRight==1 && nRowLeft>0) ||
+            (nRowRight>=1 && nRowLeft==nRowRight);
+  if (!ok) {
+     throw (AipsError("The right Scan Table can have one row or the same number of rows as the left Scan Table"));
+  }
+
+  // Input Tables
+  const Table& tLeft = left->table();
+  const Table& tRight = right->table();
+
+  // TSys columns
+  ROArrayColumn<Float> tSysLeftCol, tSysRightCol;
+  if (doTSys) {
+    tSysLeftCol.attach(tLeft, "TSYS");
+    tSysRightCol.attach(tRight, "TSYS");
+  }
+
+  // First row for right
+  Array<Float> tSysLeftArr, tSysRightArr;
+  if (doTSys) tSysRightCol.get(0, tSysRightArr);
+  MaskedArray<Float>* pMRight =
+    new MaskedArray<Float>(right->rowAsMaskedArray(0));
+
+  IPosition shpRight = pMRight->shape();
+
+  // Output Table cloned from left
+  SDMemTable* pTabOut = new SDMemTable(*left, True);
+  pTabOut->appendToHistoryTable(right->getHistoryTable());
+
+  // Loop over rows
+  for (uInt i=0; i<nRowLeft; i++) {
+
+    // Get data
+    MaskedArray<Float> mLeft(left->rowAsMaskedArray(i));
+    IPosition shpLeft = mLeft.shape();
+    if (doTSys) tSysLeftCol.get(i, tSysLeftArr);
+
+    if (nRowRight>1) {
+      delete pMRight;
+      pMRight = new MaskedArray<Float>(right->rowAsMaskedArray(i));
+      shpRight = pMRight->shape();
+      if (doTSys) tSysRightCol.get(i, tSysRightArr);
+    }
+
+    if (!shpRight.isEqual(shpLeft)) {
+      delete pTabOut;
+      delete pMRight;
+      throw(AipsError("left and right scan tables are not conformant"));
+    }
+    if (doTSys) {
+      if (!tSysRightArr.shape().isEqual(tSysRightArr.shape())) {
+        delete pTabOut;
+        delete pMRight;
+        throw(AipsError("left and right Tsys data are not conformant"));
+      }
+      if (!shpRight.isEqual(tSysRightArr.shape())) {
+        delete pTabOut;
+        delete pMRight;
+        throw(AipsError("left and right scan tables are not conformant"));
+      }
+    }
+
+    // Make container
+     SDContainer sc = left->getSDContainer(i);
+
+     // Operate on data and TSys
+     if (what==0) {
+        MaskedArray<Float> tmp = mLeft + *pMRight;
+        putDataInSDC(sc, tmp.getArray(), tmp.getMask());
+        if (doTSys) sc.putTsys(tSysLeftArr+tSysRightArr);
+     } else if (what==1) {
+        MaskedArray<Float> tmp = mLeft - *pMRight;
+        putDataInSDC(sc, tmp.getArray(), tmp.getMask());
+        if (doTSys) sc.putTsys(tSysLeftArr-tSysRightArr);
+     } else if (what==2) {
+        MaskedArray<Float> tmp = mLeft * *pMRight;
+        putDataInSDC(sc, tmp.getArray(), tmp.getMask());
+        if (doTSys) sc.putTsys(tSysLeftArr*tSysRightArr);
+     } else if (what==3) {
+        MaskedArray<Float> tmp = mLeft / *pMRight;
+        putDataInSDC(sc, tmp.getArray(), tmp.getMask());
+        if (doTSys) sc.putTsys(tSysLeftArr/tSysRightArr);
+     } else if (what==4) {
+       if (preserve) {
+         MaskedArray<Float> tmp = (tSysRightArr * mLeft / *pMRight) -
+           tSysRightArr;
+         putDataInSDC(sc, tmp.getArray(), tmp.getMask());
+       } else {
+         MaskedArray<Float> tmp = (tSysRightArr * mLeft / *pMRight) -
+           tSysLeftArr;
+         putDataInSDC(sc, tmp.getArray(), tmp.getMask());
+       }
+       sc.putTsys(tSysRightArr);
+     }
+
+     // Put new row in output Table
+     pTabOut->putSDContainer(sc);
+  }
+  if (pMRight) delete pMRight;
+  pTabOut->resetCursor();
+
+  return CountedPtr<SDMemTable>(pTabOut);
+}
+
+
+std::vector<float> SDMath::statistic(const CountedPtr<SDMemTable>& in,
+                                     const Vector<Bool>& mask,
+                                     const String& which, Int row) const
+//
+// Perhaps iteration over pol/beam/if should be in here
+// and inside the nrow iteration ?
+//
+{
+  const uInt nRow = in->nRow();
+
+// Specify cursor location
+
+  IPosition start, end;
+  Bool doAll = False;
+  setCursorSlice(start, end, doAll, *in);
+
+// Loop over rows
+
+  const uInt nEl = mask.nelements();
+  uInt iStart = 0;
+  uInt iEnd = in->nRow()-1;
+//
+  if (row>=0) {
+     iStart = row;
+     iEnd = row;
+  }
+//
+  std::vector<float> result(iEnd-iStart+1);
+  for (uInt ii=iStart; ii <= iEnd; ++ii) {
+
+// Get row and deconstruct
+
+     MaskedArray<Float> dataIn = (in->rowAsMaskedArray(ii))(start,end);
+     Array<Float> v = dataIn.getArray().nonDegenerate();
+     Array<Bool>  m = dataIn.getMask().nonDegenerate();
+
+// Access desired piece of data
+
+//     Array<Float> v((arr(start,end)).nonDegenerate());
+//     Array<Bool> m((barr(start,end)).nonDegenerate());
+
+// Apply OTF mask
+
+     MaskedArray<Float> tmp;
+     if (m.nelements()==nEl) {
+       tmp.setData(v,m&&mask);
+     } else {
+       tmp.setData(v,m);
+     }
+
+// Get statistic
+
+     result[ii-iStart] = mathutil::statistics(which, tmp);
+  }
+//
+  return result;
+}
+
+
+SDMemTable* SDMath::bin(const SDMemTable& in, Int width)
+{
+  SDHeader sh = in.getSDHeader();
+  SDMemTable* pTabOut = new SDMemTable(in, True);
+
+// Bin up SpectralCoordinates
+
+  IPosition factors(1);
+  factors(0) = width;
+  for (uInt j=0; j<in.nCoordinates(); ++j) {
+    CoordinateSystem cSys;
+    cSys.addCoordinate(in.getSpectralCoordinate(j));
+    CoordinateSystem cSysBin =
+      CoordinateUtil::makeBinnedCoordinateSystem(factors, cSys, False);
+//
+    SpectralCoordinate sCBin = cSysBin.spectralCoordinate(0);
+    pTabOut->setCoordinate(sCBin, j);
+  }
+
+// Use RebinLattice to find shape
+
+  IPosition shapeIn(1,sh.nchan);
+  IPosition shapeOut = RebinLattice<Float>::rebinShape(shapeIn, factors);
+  sh.nchan = shapeOut(0);
+  pTabOut->putSDHeader(sh);
+
+// Loop over rows and bin along channel axis
+
+  for (uInt i=0; i < in.nRow(); ++i) {
+    SDContainer sc = in.getSDContainer(i);
+//
+    Array<Float> tSys(sc.getTsys());                           // Get it out before sc changes shape
+
+// Bin up spectrum
+
+    MaskedArray<Float> marr(in.rowAsMaskedArray(i));
+    MaskedArray<Float> marrout;
+    LatticeUtilities::bin(marrout, marr, asap::ChanAxis, width);
+
+// Put back the binned data and flags
+
+    IPosition ip2 = marrout.shape();
+    sc.resize(ip2);
+//
+    putDataInSDC(sc, marrout.getArray(), marrout.getMask());
+
+// Bin up Tsys.
+
+    Array<Bool> allGood(tSys.shape(),True);
+    MaskedArray<Float> tSysIn(tSys, allGood, True);
+//
+    MaskedArray<Float> tSysOut;
+    LatticeUtilities::bin(tSysOut, tSysIn, asap::ChanAxis, width);
+    sc.putTsys(tSysOut.getArray());
+//
+    pTabOut->putSDContainer(sc);
+  }
+  return pTabOut;
+}
+
+SDMemTable* SDMath::resample(const SDMemTable& in, const String& methodStr,
+                             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.
+//
+{
+   Bool doVel = False;
+   if (doVel) {
+      for (uInt j=0; j<in.nCoordinates(); ++j) {
+         SpectralCoordinate sC = in.getSpectralCoordinate(j);
+      }
+   }
+
+// Interpolation method
+
+  InterpolateArray1D<Double,Float>::InterpolationMethod interp;
+  convertInterpString(interp, methodStr);
+  Int interpMethod(interp);
+
+// Make output table
+
+  SDMemTable* pTabOut = new SDMemTable(in, True);
+
+// Resample SpectralCoordinates (one per freqID)
+
+  const uInt nCoord = in.nCoordinates();
+  Vector<Float> offset(1,0.0);
+  Vector<Float> factors(1,1.0/width);
+  Vector<Int> newShape;
+  for (uInt j=0; j<in.nCoordinates(); ++j) {
+    CoordinateSystem cSys;
+    cSys.addCoordinate(in.getSpectralCoordinate(j));
+    CoordinateSystem cSys2 = cSys.subImage(offset, factors, newShape);
+    SpectralCoordinate sC = cSys2.spectralCoordinate(0);
+//
+    pTabOut->setCoordinate(sC, j);
+  }
+
+// Get header
+
+  SDHeader sh = in.getSDHeader();
+
+// Generate resampling vectors
+
+  const uInt nChanIn = sh.nchan;
+  Vector<Float> xIn(nChanIn);
+  indgen(xIn);
+//
+  Int fac =  Int(nChanIn/width);
+  Vector<Float> xOut(fac+10);          // 10 to be safe - resize later
+  uInt i = 0;
+  Float x = 0.0;
+  Bool more = True;
+  while (more) {
+    xOut(i) = x;
+//
+    i++;
+    x += width;
+    if (x>nChanIn-1) more = False;
+  }
+  const uInt nChanOut = i;
+  xOut.resize(nChanOut,True);
+//
+  IPosition shapeIn(in.rowAsMaskedArray(0).shape());
+  sh.nchan = nChanOut;
+  pTabOut->putSDHeader(sh);
+
+// Loop over rows and resample along channel axis
+
+  Array<Float> valuesOut;
+  Array<Bool> maskOut;
+  Array<Float> tSysOut;
+  Array<Bool> tSysMaskIn(shapeIn,True);
+  Array<Bool> tSysMaskOut;
+  for (uInt i=0; i < in.nRow(); ++i) {
+
+// Get container
+
+     SDContainer sc = in.getSDContainer(i);
+
+// Get data and Tsys
+
+     const Array<Float>& tSysIn = sc.getTsys();
+     const MaskedArray<Float>& dataIn(in.rowAsMaskedArray(i));
+     Array<Float> valuesIn = dataIn.getArray();
+     Array<Bool> maskIn = dataIn.getMask();
+
+// Interpolate data
+
+     InterpolateArray1D<Float,Float>::interpolate(valuesOut, maskOut, xOut,
+                                                  xIn, valuesIn, maskIn,
+                                                  interpMethod, True, True);
+     sc.resize(valuesOut.shape());
+     putDataInSDC(sc, valuesOut, maskOut);
+
+// Interpolate TSys
+
+     InterpolateArray1D<Float,Float>::interpolate(tSysOut, tSysMaskOut, xOut,
+                                                  xIn, tSysIn, tSysMaskIn,
+                                                  interpMethod, True, True);
+    sc.putTsys(tSysOut);
+
+// Put container in output
+
+    pTabOut->putSDContainer(sc);
+  }
+//
+  return pTabOut;
+}
+
+SDMemTable* SDMath::unaryOperate(const SDMemTable& in, Float val, Bool doAll,
+                                 uInt what, Bool doTSys)
+//
+// what = 0   Multiply
+//        1   Add
+{
+   SDMemTable* pOut = new SDMemTable(in,False);
+   const Table& tOut = pOut->table();
+   ArrayColumn<Float> specCol(tOut,"SPECTRA");
+   ArrayColumn<Float> tSysCol(tOut,"TSYS");
+   Array<Float> tSysArr;
+
+// Get data slice bounds
+
+   IPosition start, end;
+   setCursorSlice (start, end, doAll, in);
+//
+   for (uInt i=0; i<tOut.nrow(); i++) {
+
+// Modify data
+
+      MaskedArray<Float> dataIn(pOut->rowAsMaskedArray(i));
+      MaskedArray<Float> dataIn2 = dataIn(start,end);    // Reference
+      if (what==0) {
+         dataIn2 *= val;
+      } else if (what==1) {
+         dataIn2 += val;
+      }
+      specCol.put(i, dataIn.getArray());
+
+// Modify Tsys
+
+      if (doTSys) {
+         tSysCol.get(i, tSysArr);
+         Array<Float> tSysArr2 = tSysArr(start,end);     // Reference
+         if (what==0) {
+            tSysArr2 *= val;
+         } else if (what==1) {
+            tSysArr2 += val;
+         }
+         tSysCol.put(i, tSysArr);
+      }
+   }
+//
+   return pOut;
+}
+
+SDMemTable* SDMath::averagePol(const SDMemTable& in, const Vector<Bool>& mask,
+                               const String& weightStr)
+//
+// Average all polarizations together, weighted by variance
+//
+{
+   WeightType wtType = NONE;
+   convertWeightString(wtType, weightStr, True);
+
+// Create output Table and reshape number of polarizations
+
+  Bool clear=True;
+  SDMemTable* pTabOut = new SDMemTable(in, clear);
+  SDHeader header = pTabOut->getSDHeader();
+  header.npol = 1;
+  pTabOut->putSDHeader(header);
+//
+  const Table& tabIn = in.table();
+
+// Shape of input and output data
+
+  const IPosition& shapeIn = in.rowAsMaskedArray(0).shape();
+  IPosition shapeOut(shapeIn);
+  shapeOut(asap::PolAxis) = 1;                          // Average all polarizations
+  if (shapeIn(asap::PolAxis)==1) {
+    delete  pTabOut;
+    throw(AipsError("The input has only one polarisation"));
+  }
+//
+  const uInt nRows = in.nRow();
+  const uInt nChan = shapeIn(asap::ChanAxis);
+  AlwaysAssert(asap::nAxes==4,AipsError);
+  const IPosition vecShapeOut(4,1,1,1,nChan);     // A multi-dim form of a Vector shape
+  IPosition start(4), end(4);
+
+// Output arrays
+
+  Array<Float> outData(shapeOut, 0.0);
+  Array<Bool> outMask(shapeOut, True);
+  const IPosition axes(2, asap::PolAxis, asap::ChanAxis);              // pol-channel plane
+
+// Attach Tsys column if needed
+
+  ROArrayColumn<Float> tSysCol;
+  Array<Float> tSys;
+  if (wtType==TSYS) {
+     tSysCol.attach(tabIn,"TSYS");
+  }
+//
+  const Bool useMask = (mask.nelements() == shapeIn(asap::ChanAxis));
+
+// Loop over rows
+
+   for (uInt iRow=0; iRow<nRows; iRow++) {
+
+// Get data for this row
+
+      MaskedArray<Float> marr(in.rowAsMaskedArray(iRow));
+      Array<Float>& arr = marr.getRWArray();
+      const Array<Bool>& barr = marr.getMask();
+
+// Get Tsys
+
+      if (wtType==TSYS) {
+         tSysCol.get(iRow,tSys);
+      }
+
+// Make iterators to iterate by pol-channel planes
+// The tSys array is empty unless wtType=TSYS so only
+// access the iterator is that is the case
+
+      ReadOnlyArrayIterator<Float> itDataPlane(arr, axes);
+      ReadOnlyArrayIterator<Bool> itMaskPlane(barr, axes);
+      ReadOnlyArrayIterator<Float>* pItTsysPlane = 0;
+      if (wtType==TSYS)
+        pItTsysPlane = new ReadOnlyArrayIterator<Float>(tSys, axes);
+
+// Accumulations
+
+      Float fac = 1.0;
+      Vector<Float> vecSum(nChan,0.0);
+
+// Iterate through data by pol-channel planes
+
+      while (!itDataPlane.pastEnd()) {
+
+// Iterate through plane by polarization  and accumulate Vectors
+
+        Vector<Float> t1(nChan); t1 = 0.0;
+        Vector<Bool> t2(nChan); t2 = True;
+        Float tSys = 0.0;
+        MaskedArray<Float> vecSum(t1,t2);
+        Float norm = 0.0;
+        {
+           ReadOnlyVectorIterator<Float> itDataVec(itDataPlane.array(), 1);
+           ReadOnlyVectorIterator<Bool> itMaskVec(itMaskPlane.array(), 1);
+//
+           ReadOnlyVectorIterator<Float>* pItTsysVec = 0;
+           if (wtType==TSYS) {
+              pItTsysVec =
+                new ReadOnlyVectorIterator<Float>(pItTsysPlane->array(), 1);
+           }
+//
+           while (!itDataVec.pastEnd()) {
+
+// Create MA of data & mask (optionally including OTF mask) and  get variance for this spectrum
+
+              if (useMask) {
+                 const MaskedArray<Float> spec(itDataVec.vector(),
+                                               mask&&itMaskVec.vector());
+                 if (wtType==VAR) {
+                    fac = 1.0 / variance(spec);
+                 } else if (wtType==TSYS) {
+                    tSys = pItTsysVec->vector()[0];      // Drop pseudo channel dependency
+                    fac = 1.0 / tSys / tSys;
+                 }
+              } else {
+                 const MaskedArray<Float> spec(itDataVec.vector(),
+                                               itMaskVec.vector());
+                 if (wtType==VAR) {
+                    fac = 1.0 / variance(spec);
+                 } else if (wtType==TSYS) {
+                    tSys = pItTsysVec->vector()[0];      // Drop pseudo channel dependency
+                    fac = 1.0 / tSys / tSys;
+                 }
+              }
+
+// Normalize spectrum (without OTF mask) and accumulate
+
+              const MaskedArray<Float> spec(fac*itDataVec.vector(),
+                                            itMaskVec.vector());
+              vecSum += spec;
+              norm += fac;
+
+// Next
+
+              itDataVec.next();
+              itMaskVec.next();
+              if (wtType==TSYS) pItTsysVec->next();
+           }
+
+// Clean up
+
+           if (pItTsysVec) {
+              delete pItTsysVec;
+              pItTsysVec = 0;
+           }
+        }
+
+// Normalize summed spectrum
+
+        vecSum /= norm;
+
+// FInd position in input data array.  We are iterating by pol-channel
+// plane so all that will change is beam and IF and that's what we want.
+
+        IPosition pos = itDataPlane.pos();
+
+// Write out data. This is a bit messy. We have to reform the Vector
+// accumulator into an Array of shape (1,1,1,nChan)
+
+        start = pos;
+        end = pos;
+        end(asap::ChanAxis) = nChan-1;
+        outData(start,end) = vecSum.getArray().reform(vecShapeOut);
+        outMask(start,end) = vecSum.getMask().reform(vecShapeOut);
+
+// Step to next beam/IF combination
+
+        itDataPlane.next();
+        itMaskPlane.next();
+        if (wtType==TSYS) pItTsysPlane->next();
+      }
+
+// Generate output container and write it to output table
+
+      SDContainer sc = in.getSDContainer();
+      sc.resize(shapeOut);
+//
+      putDataInSDC(sc, outData, outMask);
+      pTabOut->putSDContainer(sc);
+//
+      if (wtType==TSYS) {
+         delete pItTsysPlane;
+         pItTsysPlane = 0;
+      }
+   }
+
+// Set polarization cursor to 0
+
+  pTabOut->setPol(0);
+//
+  return pTabOut;
+}
+
+
+SDMemTable* SDMath::smooth(const SDMemTable& in,
+                           const casa::String& kernelType,
+                           casa::Float width, Bool doAll)
+//
+// Should smooth TSys as well
+//
+{
+
+  // Number of channels
+   const uInt nChan = in.nChan();
+
+   // Generate Kernel
+   VectorKernel::KernelTypes type = VectorKernel::toKernelType(kernelType);
+   Vector<Float> kernel = VectorKernel::make(type, width, nChan, True, False);
+
+   // Generate Convolver
+   IPosition shape(1,nChan);
+   Convolver<Float> conv(kernel, shape);
+
+   // New Table
+   SDMemTable* pTabOut = new SDMemTable(in,True);
+
+   // Output Vectors
+   Vector<Float> valuesOut(nChan);
+   Vector<Bool> maskOut(nChan);
+
+   // Get data slice bounds
+   IPosition start, end;
+   setCursorSlice (start, end, doAll, in);
+
+   // Loop over rows in Table
+   for (uInt ri=0; ri < in.nRow(); ++ri) {
+
+     // Get slice of data
+      MaskedArray<Float> dataIn = in.rowAsMaskedArray(ri);
+
+      // Deconstruct and get slices which reference these arrays
+      Array<Float> valuesIn = dataIn.getArray();
+      Array<Bool> maskIn = dataIn.getMask();
+
+      Array<Float> valuesIn2 = valuesIn(start,end);       // ref to valuesIn
+      Array<Bool> maskIn2 = maskIn(start,end);
+
+      // Iterate through by spectra
+      VectorIterator<Float> itValues(valuesIn2, asap::ChanAxis);
+      VectorIterator<Bool> itMask(maskIn2, asap::ChanAxis);
+      while (!itValues.pastEnd()) {
+
+        // Smooth
+        if (kernelType==VectorKernel::HANNING) {
+          mathutil::hanning(valuesOut, maskOut, itValues.vector(),
+                            itMask.vector());
+          itMask.vector() = maskOut;
+        } else {
+          mathutil::replaceMaskByZero(itValues.vector(), itMask.vector());
+          conv.linearConv(valuesOut, itValues.vector());
+        }
+
+        itValues.vector() = valuesOut;
+        itValues.next();
+        itMask.next();
+      }
+
+      // Create and put back
+      SDContainer sc = in.getSDContainer(ri);
+      putDataInSDC(sc, valuesIn, maskIn);
+
+      pTabOut->putSDContainer(sc);
+   }
+
+  return pTabOut;
+}
+
+
+
+SDMemTable* SDMath::convertFlux(const SDMemTable& in, Float D, Float etaAp,
+                                Float JyPerK, Bool doAll)
+//
+// etaAp = aperture efficiency (-1 means find)
+// D     = geometric diameter (m)  (-1 means find)
+// JyPerK
+//
+{
+  SDHeader sh = in.getSDHeader();
+  SDMemTable* pTabOut = new SDMemTable(in, True);
+
+  // Find out how to convert values into Jy and K (e.g. units might be
+  // mJy or mK) Also automatically find out what we are converting to
+  // according to the flux unit
+  Unit fluxUnit(sh.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;
+    sh.fluxunit = "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;
+    sh.fluxunit = "Jy";
+  } else {
+    throw(AipsError("Unrecognized brightness units in Table - must be consistent with Jy or K"));
+  }
+
+  pTabOut->putSDHeader(sh);
+
+  // 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(in.nRow(), factor);
+    scaleByVector(pTabOut, in, doAll, factors, False);
+  } else if (etaAp>0.0) {
+    Bool throwIt = True;
+    Instrument inst = SDAttr::convertInstrument(sh.antennaname, throwIt);
+    SDAttr sda;
+    if (D < 0) D = sda.diameter(inst);
+    Float JyPerK = SDAttr::findJyPerK(etaAp,D);
+    ostringstream oss;
+    oss << "Jy/K = " << JyPerK;
+    pushLog(String(oss));
+    factor *= JyPerK;
+    if (toKelvin) {
+      factor = 1.0 / factor;
+    }
+
+    Vector<Float> factors(in.nRow(), factor);
+    scaleByVector(pTabOut, in, doAll, 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 (pTabOut, in, toKelvin, cFac, doAll);
+  }
+  return pTabOut;
+}
+
+
+SDMemTable* SDMath::gainElevation(const SDMemTable& in,
+                                  const Vector<Float>& coeffs,
+                                  const String& fileName,
+                                  const String& methodStr, Bool doAll)
+{
+
+  // Get header and clone output table
+  SDHeader sh = in.getSDHeader();
+  SDMemTable* pTabOut = new SDMemTable(in, True);
+
+  // Get elevation data from SDMemTable and convert to degrees
+  const Table& tab = in.table();
+  ROScalarColumn<Float> elev(tab, "ELEVATION");
+  Vector<Float> x = elev.getColumn();
+  x *= Float(180 / C::pi);                        // Degrees
+
+  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 = SDAttr::convertInstrument (sh.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 {
+       SDAttr 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 = in.nRow();
+     Vector<Float> factor(nRow);
+     for (uInt i=0; i<nRow; i++) {
+       factor[i] = 1.0 / (*pPoly)(x[i]);
+     }
+     delete pPoly;
+     scaleByVector (pTabOut, in, doAll, factor, True);
+
+  } else {
+
+    // Indicate which columns to read from ascii file
+    String col0("ELEVATION");
+    String col1("FACTOR");
+
+    // Read and correct
+
+    pushLog("Making correction from ascii Table");
+    scaleFromAsciiTable (pTabOut, in, fileName, col0, col1,
+                         methodStr, doAll, x, True);
+  }
+
+  return pTabOut;
+}
+
+
+SDMemTable* SDMath::opacity(const SDMemTable& in, Float tau, Bool doAll)
+{
+
+  // Get header and clone output table
+
+  SDHeader sh = in.getSDHeader();
+  SDMemTable* pTabOut = new SDMemTable(in, True);
+
+// Get elevation data from SDMemTable and convert to degrees
+
+  const Table& tab = in.table();
+  ROScalarColumn<Float> elev(tab, "ELEVATION");
+  Vector<Float> zDist = elev.getColumn();
+  zDist = Float(C::pi_2) - zDist;
+
+// Generate correction factor
+
+  const uInt nRow = in.nRow();
+  Vector<Float> factor(nRow);
+  Vector<Float> factor2(nRow);
+  for (uInt i=0; i<nRow; i++) {
+     factor[i] = exp(tau)/cos(zDist[i]);
+  }
+
+// Correct
+
+  scaleByVector (pTabOut, in, doAll, factor, True);
+
+  return pTabOut;
+}
+
+
+void SDMath::rotateXYPhase(SDMemTable& in, Float value, Bool doAll)
+//
+// phase in degrees
+// assumes linear correlations
+//
+{
+  if (in.nPol() != 4) {
+    throw(AipsError("You must have 4 polarizations to run this function"));
+  }
+
+   SDHeader sh = in.getSDHeader();
+   Instrument inst = SDAttr::convertInstrument(sh.antennaname, False);
+   SDAttr sdAtt;
+   if (sdAtt.feedPolType(inst) != LINEAR) {
+      throw(AipsError("Only linear polarizations are supported"));
+   }
+//
+   const Table& tabIn = in.table();
+   ArrayColumn<Float> specCol(tabIn,"SPECTRA");
+   IPosition start(asap::nAxes,0);
+   IPosition end(asap::nAxes);
+
+// Set cursor slice. Assumes shape the same for all rows
+
+   setCursorSlice (start, end, doAll, in);
+   IPosition start3(start);
+   start3(asap::PolAxis) = 2;                 // Real(XY)
+   IPosition end3(end);
+   end3(asap::PolAxis) = 2;
+//
+   IPosition start4(start);
+   start4(asap::PolAxis) = 3;                 // Imag (XY)
+   IPosition end4(end);
+   end4(asap::PolAxis) = 3;
+//
+   uInt nRow = in.nRow();
+   Array<Float> data;
+   for (uInt i=0; i<nRow;++i) {
+      specCol.get(i,data);
+      IPosition shape = data.shape();
+
+      // Get polarization slice references
+      Array<Float> C3 = data(start3,end3);
+      Array<Float> C4 = data(start4,end4);
+
+      // Rotate
+      SDPolUtil::rotatePhase(C3, C4, value);
+
+      // Put
+      specCol.put(i,data);
+   }
+}
+
+
+void SDMath::rotateLinPolPhase(SDMemTable& in, Float value, Bool doAll)
+//
+// phase in degrees
+// assumes linear correlations
+//
+{
+   if (in.nPol() != 4) {
+      throw(AipsError("You must have 4 polarizations to run this function"));
+   }
+//
+   SDHeader sh = in.getSDHeader();
+   Instrument inst = SDAttr::convertInstrument(sh.antennaname, False);
+   SDAttr sdAtt;
+   if (sdAtt.feedPolType(inst) != LINEAR) {
+      throw(AipsError("Only linear polarizations are supported"));
+   }
+//
+   const Table& tabIn = in.table();
+   ArrayColumn<Float> specCol(tabIn,"SPECTRA");
+   ROArrayColumn<Float> stokesCol(tabIn,"STOKES");
+   IPosition start(asap::nAxes,0);
+   IPosition end(asap::nAxes);
+
+// Set cursor slice. Assumes shape the same for all rows
+
+   setCursorSlice (start, end, doAll, in);
+//
+   IPosition start1(start);
+   start1(asap::PolAxis) = 0;                // C1 (XX)
+   IPosition end1(end);
+   end1(asap::PolAxis) = 0;
+//
+   IPosition start2(start);
+   start2(asap::PolAxis) = 1;                 // C2 (YY)
+   IPosition end2(end);
+   end2(asap::PolAxis) = 1;
+//
+   IPosition start3(start);
+   start3(asap::PolAxis) = 2;                 // C3 ( Real(XY) )
+   IPosition end3(end);
+   end3(asap::PolAxis) = 2;
+//
+   IPosition startI(start);
+   startI(asap::PolAxis) = 0;                 // I
+   IPosition endI(end);
+   endI(asap::PolAxis) = 0;
+//
+   IPosition startQ(start);
+   startQ(asap::PolAxis) = 1;                 // Q
+   IPosition endQ(end);
+   endQ(asap::PolAxis) = 1;
+//
+   IPosition startU(start);
+   startU(asap::PolAxis) = 2;                 // U
+   IPosition endU(end);
+   endU(asap::PolAxis) = 2;
+
+//
+   uInt nRow = in.nRow();
+   Array<Float> data, stokes;
+   for (uInt i=0; i<nRow;++i) {
+      specCol.get(i,data);
+      stokesCol.get(i,stokes);
+      IPosition shape = data.shape();
+
+// Get linear polarization slice references
+
+      Array<Float> C1 = data(start1,end1);
+      Array<Float> C2 = data(start2,end2);
+      Array<Float> C3 = data(start3,end3);
+
+// Get STokes slice references
+
+      Array<Float> I = stokes(startI,endI);
+      Array<Float> Q = stokes(startQ,endQ);
+      Array<Float> U = stokes(startU,endU);
+
+// Rotate
+
+      SDPolUtil::rotateLinPolPhase(C1, C2, C3, I, Q, U, value);
+
+// Put
+
+      specCol.put(i,data);
+   }
+}
+
+// 'private' functions
+
+void SDMath::convertBrightnessUnits (SDMemTable* pTabOut, const SDMemTable& in,
+                                     Bool toKelvin, Float cFac, Bool doAll)
+{
+
+// Get header
+
+   SDHeader sh = in.getSDHeader();
+   const uInt nChan = sh.nchan;
+
+// Get instrument
+
+   Bool throwIt = True;
+   Instrument inst = SDAttr::convertInstrument(sh.antennaname, throwIt);
+
+// Get Diameter (m)
+
+   SDAttr sdAtt;
+// Get epoch of first row
+
+   MEpoch dateObs = in.getEpoch(0);
+
+// Generate a Vector of correction factors. One per FreqID
+
+   SDFrequencyTable sdft = in.getSDFreqTable();
+   Vector<uInt> freqIDs;
+//
+   Vector<Float> freqs(sdft.length());
+   for (uInt i=0; i<sdft.length(); i++) {
+      freqs(i) = (nChan/2 - sdft.referencePixel(i))*sdft.increment(i) + sdft.referenceValue(i);
+   }
+//
+   Vector<Float> JyPerK = sdAtt.JyPerK(inst, dateObs, freqs);
+   ostringstream oss;
+   oss << "Jy/K = " << JyPerK;
+   pushLog(String(oss));
+   Vector<Float> factors = cFac * JyPerK;
+   if (toKelvin) factors = Float(1.0) / factors;
+
+// Get data slice bounds
+
+   IPosition start, end;
+   setCursorSlice (start, end, doAll, in);
+   const uInt ifAxis = in.getIF();
+
+// Iteration axes
+
+   IPosition axes(asap::nAxes-1,0);
+   for (uInt i=0,j=0; i<asap::nAxes; i++) {
+      if (i!=asap::IFAxis) {
+         axes(j++) = i;
+      }
+   }
+
+// Loop over rows and apply correction factor
+
+   Float factor = 1.0;
+   const uInt axis = asap::ChanAxis;
+   for (uInt i=0; i < in.nRow(); ++i) {
+
+// Get data
+
+      MaskedArray<Float> dataIn = in.rowAsMaskedArray(i);
+      Array<Float>& values = dataIn.getRWArray();           // Ref to dataIn
+      Array<Float> values2 = values(start,end);             // Ref to values to dataIn
+
+// Get SDCOntainer
+
+      SDContainer sc = in.getSDContainer(i);
+
+// Get FreqIDs
+
+      freqIDs = sc.getFreqMap();
+
+// Now the conversion factor depends only upon frequency
+// So we need to iterate through by IF only giving
+// us BEAM/POL/CHAN cubes
+
+      ArrayIterator<Float> itIn(values2, axes);
+      uInt ax = 0;
+      while (!itIn.pastEnd()) {
+        itIn.array() *= factors(freqIDs(ax));         // Writes back to dataIn
+        itIn.next();
+      }
+
+// Write out
+
+      putDataInSDC(sc, dataIn.getArray(), dataIn.getMask());
+//
+      pTabOut->putSDContainer(sc);
+   }
+}
+
+
+
+SDMemTable* SDMath::frequencyAlign(const SDMemTable& in,
+                                   MFrequency::Types freqSystem,
+                                   const String& refTime,
+                                   const String& methodStr,
+                                   Bool perFreqID)
+{
+// Get Header
+
+   SDHeader sh = in.getSDHeader();
+   const uInt nChan = sh.nchan;
+   const uInt nRows = in.nRow();
+   const uInt nIF = sh.nif;
+
+// Get Table reference
+
+   const Table& tabIn = in.table();
+
+// Get Columns from Table
+
+   ROScalarColumn<Double> mjdCol(tabIn, "TIME");
+   ROScalarColumn<String> srcCol(tabIn, "SRCNAME");
+   ROArrayColumn<uInt> fqIDCol(tabIn, "FREQID");
+   Vector<Double> times = mjdCol.getColumn();
+
+// Generate DataDesc table
+
+   Matrix<uInt> ddIdx;
+   SDDataDesc dDesc;
+   generateDataDescTable(ddIdx, dDesc, nIF, in, tabIn, srcCol,
+                          fqIDCol, perFreqID);
+
+// 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) {
+     refEpoch = epochFromString(refTime, in.getTimeReference());
+   } else {
+     refEpoch = in.getEpoch(0);
+   }
+   ostringstream oss;
+   oss << "Aligned at reference Epoch " << formatEpoch(refEpoch) << " in frame " << MFrequency::showType(freqSystem);
+   pushLog(String(oss));
+
+   // Get Reference Position
+
+   MPosition refPos = in.getAntennaPosition();
+
+   // Create FrequencyAligner Block. One FA for each possible
+   // source/freqID (perFreqID=True) or source/IF (perFreqID=False)
+   // combination
+
+   PtrBlock<FrequencyAligner<Float>* > a(dDesc.length());
+   generateFrequencyAligners(a, dDesc, in, nChan, freqSystem, refPos,
+                              refEpoch, perFreqID);
+
+   // Generate and fill output Frequency Table.  WHen perFreqID=True,
+   // there is one output FreqID for each entry in the SDDataDesc
+   // table.  However, in perFreqID=False mode, there may be some
+   // degeneracy, so we need a little translation map
+
+   SDFrequencyTable freqTabOut = in.getSDFreqTable();
+   freqTabOut.setLength(0);
+   Vector<String> units(1);
+   units = String("Hz");
+   Bool linear=True;
+   //
+   Vector<uInt> ddFQTrans(dDesc.length(),0);
+   for (uInt i=0; i<dDesc.length(); i++) {
+
+     // Get Aligned SC in Hz
+
+     SpectralCoordinate sC = a[i]->alignedSpectralCoordinate(linear);
+     sC.setWorldAxisUnits(units);
+
+     // Add FreqID
+
+     uInt idx = freqTabOut.addFrequency(sC.referencePixel()[0],
+                                        sC.referenceValue()[0],
+                                        sC.increment()[0]);
+     // output FreqID = ddFQTrans(ddIdx)
+     ddFQTrans(i) = idx;
+   }
+
+   // Interpolation method
+
+   InterpolateArray1D<Double,Float>::InterpolationMethod interp;
+   convertInterpString(interp, methodStr);
+
+   // New output Table
+
+   //pushLog("Create output table");
+   SDMemTable* pTabOut = new SDMemTable(in,True);
+   pTabOut->putSDFreqTable(freqTabOut);
+
+   // Loop over rows in Table
+
+   Bool extrapolate=False;
+   const IPosition polChanAxes(2, asap::PolAxis, asap::ChanAxis);
+   Bool useCachedAbcissa = False;
+   Bool first = True;
+   Bool ok;
+   Vector<Float> yOut;
+   Vector<Bool> maskOut;
+   Vector<uInt> freqID(nIF);
+   uInt ifIdx, faIdx;
+   Vector<Double> xIn;
+   //
+   for (uInt iRow=0; iRow<nRows; ++iRow) {
+//      if (iRow%10==0) {
+//        ostringstream oss;
+//        oss << "Processing row " << iRow;
+//        pushLog(String(oss));
+//      }
+
+     // Get EPoch
+
+     Quantum<Double> tQ2(times[iRow],DAY);
+     MVEpoch mv2(tQ2);
+     MEpoch epoch(mv2, epochRef);
+
+     // Get copy of data
+
+     const MaskedArray<Float>& mArrIn(in.rowAsMaskedArray(iRow));
+     Array<Float> values = mArrIn.getArray();
+     Array<Bool> mask = mArrIn.getMask();
+
+     // For each row, the Frequency abcissa will be the same
+     // regardless of polarization.  For all other axes (IF and BEAM)
+     // the abcissa will change.  So we iterate through the data by
+     // pol-chan planes to mimimize the work.  Probably won't work for
+     // multiple beams at this point.
+
+     ArrayIterator<Float> itValuesPlane(values, polChanAxes);
+     ArrayIterator<Bool> itMaskPlane(mask, polChanAxes);
+     while (!itValuesPlane.pastEnd()) {
+
+       // Find the IF index and then the FA PtrBlock index
+
+       const IPosition& pos = itValuesPlane.pos();
+       ifIdx = pos(asap::IFAxis);
+       faIdx = ddIdx(iRow,ifIdx);
+
+       // Generate abcissa for perIF.  Could cache this in a Matrix on
+       // a per scan basis.  Pretty expensive doing it for every row.
+
+       if (!perFreqID) {
+         xIn.resize(nChan);
+         uInt fqID = dDesc.secID(ddIdx(iRow,ifIdx));
+         SpectralCoordinate sC = in.getSpectralCoordinate(fqID);
+           Double w;
+           for (uInt i=0; i<nChan; i++) {
+             sC.toWorld(w,Double(i));
+              xIn[i] = w;
+           }
+       }
+
+       VectorIterator<Float> itValuesVec(itValuesPlane.array(), 1);
+       VectorIterator<Bool> itMaskVec(itMaskPlane.array(), 1);
+
+       // Iterate through the plane by vector and align
+
+        first = True;
+        useCachedAbcissa=False;
+        while (!itValuesVec.pastEnd()) {
+          if (perFreqID) {
+            ok = a[faIdx]->align (yOut, maskOut, itValuesVec.vector(),
+                                  itMaskVec.vector(), epoch, useCachedAbcissa,
+                                  interp, extrapolate);
+          } else {
+            ok = a[faIdx]->align (yOut, maskOut, xIn, itValuesVec.vector(),
+                                  itMaskVec.vector(), epoch, useCachedAbcissa,
+                                  interp, extrapolate);
+          }
+          //
+          itValuesVec.vector() = yOut;
+          itMaskVec.vector() = maskOut;
+          //
+          itValuesVec.next();
+          itMaskVec.next();
+          //
+          if (first) {
+            useCachedAbcissa = True;
+            first = False;
+          }
+        }
+        //
+        itValuesPlane.next();
+        itMaskPlane.next();
+     }
+
+     // Create SDContainer and put back
+
+     SDContainer sc = in.getSDContainer(iRow);
+     putDataInSDC(sc, values, mask);
+
+     // Set output FreqIDs
+
+     for (uInt i=0; i<nIF; i++) {
+       uInt idx = ddIdx(iRow,i);               // Index into SDDataDesc table
+       freqID(i) = ddFQTrans(idx);             // FreqID in output FQ table
+     }
+     sc.putFreqMap(freqID);
+     //
+     pTabOut->putSDContainer(sc);
+   }
+
+   // Now we must set the base and extra frames to the input frame
+   std::vector<string> info = pTabOut->getCoordInfo();
+   info[1] = MFrequency::showType(freqSystem);   // Conversion frame
+   info[3] = info[1];                            // Base frame
+   pTabOut->setCoordInfo(info);
+
+   // Clean up PointerBlock
+   for (uInt i=0; i<a.nelements(); i++) delete a[i];
+
+   return pTabOut;
+}
+
+
+SDMemTable* SDMath::frequencySwitch(const SDMemTable& in)
+{
+  if (in.nIF() != 2) {
+    throw(AipsError("nIF != 2 "));
+  }
+  Bool clear = True;
+  SDMemTable* pTabOut = new SDMemTable(in, clear);
+  const Table& tabIn = in.table();
+
+  // Shape of input and output data
+  const IPosition& shapeIn = in.rowAsMaskedArray(0).shape();
+
+  const uInt nRows = in.nRow();
+  AlwaysAssert(asap::nAxes==4,AipsError);
+
+  ROArrayColumn<Float> tSysCol;
+  Array<Float> tsys;
+  tSysCol.attach(tabIn,"TSYS");
+
+  for (uInt iRow=0; iRow<nRows; iRow++) {
+    // Get data for this row
+    MaskedArray<Float> marr(in.rowAsMaskedArray(iRow));
+    tSysCol.get(iRow, tsys);
+
+    // whole Array for IF 0
+    IPosition start(asap::nAxes,0);
+    IPosition end = shapeIn-1;
+    end(asap::IFAxis) = 0;
+
+    MaskedArray<Float> on = marr(start,end);
+    Array<Float> ton = tsys(start,end);
+    // Make a copy as "src" is a refrence which is manipulated.
+    // oncopy is needed for the inverse quotient
+    MaskedArray<Float> oncopy = on.copy();
+
+    // whole Array for IF 1
+    start(asap::IFAxis) = 1;
+    end(asap::IFAxis) = 1;
+
+    MaskedArray<Float> off = marr(start,end);
+    Array<Float> toff = tsys(start,end);
+
+    on /= off; on -= 1.0f;
+    on *= ton;
+    off /= oncopy; off -= 1.0f;
+    off *= toff;
+
+    SDContainer sc = in.getSDContainer(iRow);
+    putDataInSDC(sc, marr.getArray(), marr.getMask());
+    pTabOut->putSDContainer(sc);
+  }
+  return pTabOut;
+}
+
+void SDMath::fillSDC(SDContainer& sc,
+                     const Array<Bool>& mask,
+                     const Array<Float>& data,
+                     const Array<Float>& tSys,
+                     Int scanID, Double timeStamp,
+                     Double interval, const String& sourceName,
+                     const Vector<uInt>& freqID)
+{
+// Data and mask
+
+  putDataInSDC(sc, data, mask);
+
+// TSys
+
+  sc.putTsys(tSys);
+
+// Time things
+
+  sc.timestamp = timeStamp;
+  sc.interval = interval;
+  sc.scanid = scanID;
+//
+  sc.sourcename = sourceName;
+  sc.putFreqMap(freqID);
+}
+
+void SDMath::accumulate(Double& timeSum, Double& intSum, Int& nAccum,
+                        MaskedArray<Float>& sum, Array<Float>& sumSq,
+                        Array<Float>& nPts, Array<Float>& tSysSum,
+                        Array<Float>& tSysSqSum,
+                        const Array<Float>& tSys, const Array<Float>& nInc,
+                        const Vector<Bool>& mask, Double time, Double interval,
+                        const std::vector<CountedPtr<SDMemTable> >& in,
+                        uInt iTab, uInt iRow, uInt axis,
+                        uInt nAxesSub, Bool useMask,
+                        WeightType wtType)
+{
+
+// Get data
+
+   MaskedArray<Float> dataIn(in[iTab]->rowAsMaskedArray(iRow));
+   Array<Float>& valuesIn = dataIn.getRWArray();           // writable reference
+   const Array<Bool>& maskIn = dataIn.getMask();          // RO reference
+//
+   if (wtType==NONE) {
+      const MaskedArray<Float> n(nInc,dataIn.getMask());
+      nPts += n;                               // Only accumulates where mask==T
+   } else if (wtType==TINT) {
+
+// We are weighting the data by integration time.
+
+     valuesIn *= Float(interval);
+
+   } else if (wtType==VAR) {
+
+// We are going to average the data, weighted by the noise for each pol, beam and IF.
+// So therefore we need to iterate through by spectrum (axis 3)
+
+      VectorIterator<Float> itData(valuesIn, axis);
+      ReadOnlyVectorIterator<Bool> itMask(maskIn, axis);
+      Float fac = 1.0;
+      IPosition pos(nAxesSub,0);
+//
+      while (!itData.pastEnd()) {
+
+// Make MaskedArray of Vector, optionally apply OTF mask, and find scaling factor
+
+         if (useMask) {
+            MaskedArray<Float> tmp(itData.vector(),mask&&itMask.vector());
+            fac = 1.0/variance(tmp);
+         } else {
+            MaskedArray<Float> tmp(itData.vector(),itMask.vector());
+            fac = 1.0/variance(tmp);
+         }
+
+// Scale data
+
+         itData.vector() *= fac;     // Writes back into 'dataIn'
+//
+// Accumulate variance per if/pol/beam averaged over spectrum
+// This method to get pos2 from itData.pos() is only valid
+// because the spectral axis is the last one (so we can just
+// copy the first nAXesSub positions out)
+
+         pos = itData.pos().getFirst(nAxesSub);
+         sumSq(pos) += fac;
+//
+         itData.next();
+         itMask.next();
+      }
+   } else if (wtType==TSYS || wtType==TINTSYS) {
+
+// We are going to average the data, weighted by 1/Tsys**2 for each pol, beam and IF.
+// So therefore we need to iterate through by spectrum (axis 3).  Although
+// Tsys is stored as a vector of length nChan, the values are replicated.
+// We will take a short cut and just use the value from the first channel
+// for now.
+//
+      VectorIterator<Float> itData(valuesIn, axis);
+      ReadOnlyVectorIterator<Float> itTSys(tSys, axis);
+      IPosition pos(nAxesSub,0);
+//
+      Float fac = 1.0;
+      if (wtType==TINTSYS) fac *= interval;
+      while (!itData.pastEnd()) {
+         Float t = itTSys.vector()[0];
+         fac *= 1.0/t/t;
+
+// Scale data
+
+         itData.vector() *= fac;     // Writes back into 'dataIn'
+//
+// Accumulate Tsys  per if/pol/beam averaged over spectrum
+// This method to get pos2 from itData.pos() is only valid
+// because the spectral axis is the last one (so we can just
+// copy the first nAXesSub positions out)
+
+         pos = itData.pos().getFirst(nAxesSub);
+         tSysSqSum(pos) += fac;
+//
+         itData.next();
+         itTSys.next();
+      }
+   }
+
+// Accumulate sum of (possibly scaled) data
+
+   sum += dataIn;
+
+// Accumulate Tsys, time, and interval
+
+   tSysSum += tSys;
+   timeSum += time;
+   intSum += interval;
+   nAccum += 1;
+}
+
+
+void SDMath::normalize(MaskedArray<Float>& sum,
+                       const Array<Float>& sumSq,
+                       const Array<Float>& tSysSqSum,
+                       const Array<Float>& nPts,
+                       Double intSum,
+                       WeightType wtType, Int axis,
+                       Int nAxesSub)
+{
+   IPosition pos2(nAxesSub,0);
+//
+   if (wtType==NONE) {
+
+// We just average by the number of points accumulated.
+// We need to make a MA out of nPts so that no divide by
+// zeros occur
+
+      MaskedArray<Float> t(nPts, (nPts>Float(0.0)));
+      sum /= t;
+   } else if (wtType==TINT) {
+
+// Average by sum of Tint
+
+      sum /= Float(intSum);
+   } else if (wtType==VAR) {
+
+// Normalize each spectrum by sum(1/var) where the variance
+// is worked out for each spectrum
+
+      Array<Float>& data = sum.getRWArray();
+      VectorIterator<Float> itData(data, axis);
+      while (!itData.pastEnd()) {
+         pos2 = itData.pos().getFirst(nAxesSub);
+         itData.vector() /= sumSq(pos2);
+         itData.next();
+      }
+   } else if (wtType==TSYS || wtType==TINTSYS) {
+
+// Normalize each spectrum by sum(1/Tsys**2) (TSYS) or
+// sum(Tint/Tsys**2) (TINTSYS) where the pseudo
+// replication over channel for Tsys has been dropped.
+
+      Array<Float>& data = sum.getRWArray();
+      VectorIterator<Float> itData(data, axis);
+      while (!itData.pastEnd()) {
+         pos2 = itData.pos().getFirst(nAxesSub);
+         itData.vector() /= tSysSqSum(pos2);
+         itData.next();
+      }
+   }
+}
+
+
+
+
+void SDMath::setCursorSlice (IPosition& start, IPosition& end, Bool doAll, const SDMemTable& in) const
+{
+  const uInt nDim = asap::nAxes;
+  DebugAssert(nDim==4,AipsError);
+//
+  start.resize(nDim);
+  end.resize(nDim);
+  if (doAll) {
+     start = 0;
+     end(0) = in.nBeam()-1;
+     end(1) = in.nIF()-1;
+     end(2) = in.nPol()-1;
+     end(3) = in.nChan()-1;
+  } else {
+     start(0) = in.getBeam();
+     end(0) = start(0);
+//
+     start(1) = in.getIF();
+     end(1) = start(1);
+//
+     start(2) = in.getPol();
+     end(2) = start(2);
+//
+     start(3) = 0;
+     end(3) = in.nChan()-1;
+   }
+}
+
+
+void SDMath::convertWeightString(WeightType& wtType, const String& weightStr,
+                                 Bool listType)
+{
+  String tStr(weightStr);
+  tStr.upcase();
+  String msg;
+  if (tStr.contains(String("NONE"))) {
+     wtType = NONE;
+     msg = String("Weighting type selected : None");
+  } else if (tStr.contains(String("VAR"))) {
+     wtType = VAR;
+     msg = String("Weighting type selected : Variance");
+  } else if (tStr.contains(String("TINTSYS"))) {
+       wtType = TINTSYS;
+       msg = String("Weighting type selected : Tint&Tsys");
+  } else if (tStr.contains(String("TINT"))) {
+     wtType = TINT;
+     msg = String("Weighting type selected : Tint");
+  } else if (tStr.contains(String("TSYS"))) {
+     wtType = TSYS;
+     msg = String("Weighting type selected : Tsys");
+  } else {
+     msg = String("Weighting type selected : None");
+     throw(AipsError("Unrecognized weighting type"));
+  }
+//
+  if (listType) pushLog(msg);
+}
+
+
+void SDMath::convertInterpString(casa::InterpolateArray1D<Double,Float>::InterpolationMethod& type,
+                                 const casa::String& interp)
+{
+  String tStr(interp);
+  tStr.upcase();
+  if (tStr.contains(String("NEAR"))) {
+     type = InterpolateArray1D<Double,Float>::nearestNeighbour;
+  } else if (tStr.contains(String("LIN"))) {
+     type = InterpolateArray1D<Double,Float>::linear;
+  } else if (tStr.contains(String("CUB"))) {
+     type = InterpolateArray1D<Double,Float>::cubic;
+  } else if (tStr.contains(String("SPL"))) {
+     type = InterpolateArray1D<Double,Float>::spline;
+  } else {
+    throw(AipsError("Unrecognized interpolation type"));
+  }
+}
+
+void SDMath::putDataInSDC(SDContainer& sc, const Array<Float>& data,
+                          const Array<Bool>& mask)
+{
+    sc.putSpectrum(data);
+//
+    Array<uChar> outflags(data.shape());
+    convertArray(outflags,!mask);
+    sc.putFlags(outflags);
+}
+
+Table SDMath::readAsciiFile(const String& fileName) const
+{
+   String formatString;
+   Table tbl = readAsciiTable (formatString, Table::Memory, fileName, "", "", False);
+   return tbl;
+}
+
+
+
+void SDMath::scaleFromAsciiTable(SDMemTable* pTabOut,
+                                 const SDMemTable& in, const String& fileName,
+                                 const String& col0, const String& col1,
+                                 const String& methodStr, Bool doAll,
+                                 const Vector<Float>& xOut, Bool doTSys)
+{
+
+// Read gain-elevation ascii file data into a Table.
+
+  Table geTable = readAsciiFile (fileName);
+//
+  scaleFromTable (pTabOut, in, geTable, col0, col1, methodStr, doAll, xOut, doTSys);
+}
+
+void SDMath::scaleFromTable(SDMemTable* pTabOut, const SDMemTable& in,
+                            const Table& tTable, const String& col0,
+                            const String& col1,
+                            const String& methodStr, Bool doAll,
+                            const Vector<Float>& xOut, Bool doTsys)
+{
+
+// Get data from Table
+
+  ROScalarColumn<Float> geElCol(tTable, col0);
+  ROScalarColumn<Float> geFacCol(tTable, col1);
+  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 method;
+   convertInterpString(method, methodStr);
+   Int intMethod(method);
+//
+   Vector<Float> yOut;
+   Vector<Bool> maskOut;
+   InterpolateArray1D<Float,Float>::interpolate(yOut, maskOut, xOut,
+                                                xIn, yIn, maskIn, intMethod,
+                                                True, True);
+// Apply
+
+   scaleByVector(pTabOut, in, doAll, Float(1.0)/yOut, doTsys);
+}
+
+
+void SDMath::scaleByVector(SDMemTable* pTabOut, const SDMemTable& in,
+                           Bool doAll, const Vector<Float>& factor,
+                           Bool doTSys)
+{
+
+// Set up data slice
+
+  IPosition start, end;
+  setCursorSlice (start, end, doAll, in);
+
+// Get Tsys column
+
+  const Table& tIn = in.table();
+  ArrayColumn<Float> tSysCol(tIn, "TSYS");
+  Array<Float> tSys;
+
+// Loop over rows and apply correction factor
+
+  const uInt axis = asap::ChanAxis;
+  for (uInt i=0; i < in.nRow(); ++i) {
+
+// Get data
+
+     MaskedArray<Float> dataIn(in.rowAsMaskedArray(i));
+     MaskedArray<Float> dataIn2 = dataIn(start,end);  // reference to dataIn
+//
+     if (doTSys) {
+        tSysCol.get(i, tSys);
+        Array<Float> tSys2 = tSys(start,end) * factor[i];
+        tSysCol.put(i, tSys);
+     }
+
+// Apply factor
+
+     dataIn2 *= factor[i];
+
+// Write out
+
+     SDContainer sc = in.getSDContainer(i);
+     putDataInSDC(sc, dataIn.getArray(), dataIn.getMask());
+//
+     pTabOut->putSDContainer(sc);
+  }
+}
+
+
+
+
+void SDMath::generateDataDescTable (Matrix<uInt>& ddIdx,
+                                    SDDataDesc& dDesc,
+                                    uInt nIF,
+                                    const SDMemTable& in,
+                                    const Table& tabIn,
+                                    const ROScalarColumn<String>& srcCol,
+                                    const ROArrayColumn<uInt>& fqIDCol,
+                                    Bool perFreqID)
+{
+   const uInt nRows = tabIn.nrow();
+   ddIdx.resize(nRows,nIF);
+//
+   String srcName;
+   Vector<uInt> freqIDs;
+   for (uInt iRow=0; iRow<nRows; iRow++) {
+      srcCol.get(iRow, srcName);
+      fqIDCol.get(iRow, freqIDs);
+      const MDirection& dir = in.getDirection(iRow);
+//
+      if (perFreqID) {
+
+// One entry per source/freqID pair
+
+         for (uInt iIF=0; iIF<nIF; iIF++) {
+            ddIdx(iRow,iIF) = dDesc.addEntry(srcName, freqIDs[iIF], dir, 0);
+         }
+      } else {
+
+// One entry per source/IF pair.  Hang onto the FreqID as well
+
+         for (uInt iIF=0; iIF<nIF; iIF++) {
+            ddIdx(iRow,iIF) = dDesc.addEntry(srcName, iIF, dir, freqIDs[iIF]);
+         }
+      }
+   }
+}
+
+
+
+
+
+MEpoch SDMath::epochFromString(const String& str, MEpoch::Types timeRef)
+{
+   Quantum<Double> qt;
+   if (MVTime::read(qt,str)) {
+      MVEpoch mv(qt);
+      MEpoch me(mv, timeRef);
+      return me;
+   } else {
+      throw(AipsError("Invalid format for Epoch string"));
+   }
+}
+
+
+String SDMath::formatEpoch(const MEpoch& epoch)  const
+{
+   MVTime mvt(epoch.getValue());
+   return mvt.string(MVTime::YMD) + String(" (") + epoch.getRefString() + String(")");
+}
+
+
+
+void SDMath::generateFrequencyAligners(PtrBlock<FrequencyAligner<Float>* >& a,
+                                       const SDDataDesc& dDesc,
+                                       const SDMemTable& in, uInt nChan,
+                                       MFrequency::Types system,
+                                       const MPosition& refPos,
+                                       const MEpoch& refEpoch,
+                                       Bool perFreqID)
+{
+   for (uInt i=0; i<dDesc.length(); i++) {
+      uInt ID = dDesc.ID(i);
+      uInt secID = dDesc.secID(i);
+      const MDirection& refDir = dDesc.secDir(i);
+//
+      if (perFreqID) {
+
+// One aligner per source/FreqID pair.
+
+         SpectralCoordinate sC = in.getSpectralCoordinate(ID);
+         a[i] = new FrequencyAligner<Float>(sC, nChan, refEpoch, refDir, refPos, system);
+      } else {
+
+// One aligner per source/IF pair.  But we still need the FreqID to
+// get the right SC.  Hence the messing about with the secondary ID
+
+         SpectralCoordinate sC = in.getSpectralCoordinate(secID);
+         a[i] = new FrequencyAligner<Float>(sC, nChan, refEpoch, refDir, refPos, system);
+      }
+   }
+}
+
+Vector<uInt> SDMath::getRowRange(const SDMemTable& in) const
+{
+   Vector<uInt> range(2);
+   range[0] = 0;
+   range[1] = in.nRow()-1;
+   return range;
+}
+
+
+Bool SDMath::rowInRange(uInt i, const Vector<uInt>& range) const
+{
+   return (i>=range[0] && i<=range[1]);
+}
+
Index: tags/asap1/src/SDMath.h
===================================================================
--- tags/asap1/src/SDMath.h	(revision 819)
+++ tags/asap1/src/SDMath.h	(revision 819)
@@ -0,0 +1,268 @@
+//#---------------------------------------------------------------------------
+//# SDMath.h: A collection of single dish mathematical 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 SDMATH_H
+#define SDMATH_H
+
+#include <string>
+#include <vector>
+#include <casa/aips.h>
+#include <casa/Utilities/CountedPtr.h>
+#include <coordinates/Coordinates/FrequencyAligner.h>
+
+#include "SDDefs.h"
+#include "SDLog.h"
+
+class casa::Table;
+class casa::MEpoch;
+class casa::MPosition;
+template<class T> class casa::PtrBlock;
+template<class T> class casa::Matrix;
+template<class T> class casa::ROScalarColumn;
+template<class T> class casa::ROArrayColumn;
+
+namespace asap {
+
+class SDMemTable;
+class SDDataDesc;
+  
+class SDMath : private SDLog {
+
+public:
+  
+  // Default constructor 
+  SDMath();
+  
+  // Copy Constructor (copy semantics)
+  SDMath(const SDMath& other);
+  
+  // Assignment  (copy semantics)
+  SDMath &operator=(const SDMath& other);
+  
+  // Destructor
+  ~SDMath();
+  
+  // Binary Table operators. op=ADD, SUB, MUL, DIV, QUOTIENT
+  casa::CountedPtr<SDMemTable> binaryOperate(const casa::CountedPtr<SDMemTable>& left,
+					     const casa::CountedPtr<SDMemTable>& right,
+					     const casa::String& op, casa::Bool preserve,
+					     casa::Bool tSys);
+    
+    // Average in time
+    casa::CountedPtr<SDMemTable> average(const std::vector<casa::CountedPtr<SDMemTable> >& in,
+					 const casa::Vector<casa::Bool>& mask,
+					 casa::Bool scanAverage, 
+					 const casa::String& weightStr,
+					 casa::Bool align=casa::False);
+    
+  // Statistics. If row<0, all rows are done otherwise, just the 
+  // specified row.
+  std::vector<float> statistic(const casa::CountedPtr<SDMemTable>& in, 
+			       const casa::Vector<casa::Bool>& mask, 
+			       const casa::String& which, casa::Int row) const;
+			       
+// Bin up spectra
+   SDMemTable* bin(const SDMemTable& in, casa::Int width);
+
+  // Resample spectra
+   SDMemTable* resample(const SDMemTable& in, const casa::String& method, 
+                        casa::Float factor);
+
+// Smooth
+  SDMemTable* smooth(const SDMemTable& in, const casa::String& kernel,
+		      casa::Float width, casa::Bool doAll);
+
+// Flux conversion between Jansky and Kelvin
+   SDMemTable* convertFlux(const SDMemTable& in, casa::Float D, 
+			   casa::Float etaAp, 
+			   casa::Float JyPerK, casa::Bool doAll);
+
+// Gain-elevation correction
+   SDMemTable* gainElevation(const SDMemTable& in, 
+			     const casa::Vector<casa::Float>& coeffs,
+			     const casa::String& fileName,
+			     const casa::String& method, 
+			     casa::Bool doAll);
+
+// Frequency Alignment
+   SDMemTable* frequencyAlignment(const SDMemTable& in, const casa::String& refTime,
+                                   const casa::String& method, casa::Bool perFreqID);
+
+// Opacity correction
+   SDMemTable* opacity(const SDMemTable& in, casa::Float tau, casa::Bool doAll);
+
+// Simple unary mathematical operations.  what=0 (mul) or 1 (add)
+   SDMemTable* unaryOperate(const SDMemTable& in, casa::Float offset, 
+                            casa::Bool doAll, casa::uInt what, casa::Bool tSys);
+
+  // Average polarizations.
+  SDMemTable* averagePol(const SDMemTable& in, 
+			 const casa::Vector<casa::Bool>& mask,
+			 const casa::String& wtStr);
+
+  SDMemTable* frequencySwitch(const SDMemTable& in);
+  
+
+// Rotate XY phase. Value in degrees.
+   void rotateXYPhase(SDMemTable& in, casa::Float value, casa::Bool doAll);
+
+// Rotate Q & U by operating on the raw correlations.Value in degrees.
+   void rotateLinPolPhase(SDMemTable& in, casa::Float value, casa::Bool doAll);
+
+  
+ private:
+
+// Weighting type for time averaging
+
+  enum WeightType {NONE=0,VAR,TSYS,TINT,TINTSYS,nWeightTypes};
+
+// Function to use accumulate data during time averaging
+
+  void accumulate(casa::Double& timeSum, casa::Double& intSum, 
+		  casa::Int& nAccum,
+		  casa::MaskedArray<casa::Float>& sum, 
+		  casa::Array<casa::Float>& sumSq,
+		  casa::Array<casa::Float>& nPts, 
+		  casa::Array<casa::Float>& tSysSum,
+		  casa::Array<casa::Float>& tSysSqSum,
+		  const casa::Array<casa::Float>& tSys,  
+		  const casa::Array<casa::Float>& nInc,
+		  const casa::Vector<casa::Bool>& mask, 
+		  casa::Double time, casa::Double interval,
+		  const std::vector<casa::CountedPtr<SDMemTable> >& in,
+		  casa::uInt iTab, casa::uInt iRow, 
+		  casa::uInt axis, casa::uInt nAxesSub,
+		  casa::Bool useMask, WeightType wtType);
+
+// Work out conversion factor for converting Jy<->K per IF per row and apply
+    void convertBrightnessUnits(SDMemTable* pTabOut, const SDMemTable& in, 
+                                casa::Bool toKelvin, casa::Float sFac, casa::Bool doAll);
+
+// Convert weight string to enum value
+
+   void convertWeightString(WeightType& wt, const casa::String& weightStr,
+                             casa::Bool listType);;
+
+// Convert interpolation type string
+//   void convertInterpString(casa::Int& type, const casa::String& interp);
+   void convertInterpString(casa::InterpolateArray1D<casa::Double,casa::Float>::InterpolationMethod& method,  
+                             const casa::String& interp);
+
+// Scale data with values from an ascii Table
+   void scaleFromAsciiTable(SDMemTable* pTabOut, const SDMemTable& in, 
+                              const casa::String& fileName,
+                              const casa::String& col0, const casa::String& col1,
+                              const casa::String& methodStr, casa::Bool doAll,
+                              const casa::Vector<casa::Float>& xOut, casa::Bool doTSys);
+
+// Scale data with values from a Table
+   void scaleFromTable(SDMemTable* pTabOut, const SDMemTable& in, const casa::Table& tTable,
+                         const casa::String& col0, const casa::String& col1,
+                         const casa::String& methodStr, casa::Bool doAll,
+                         const casa::Vector<casa::Float>& xOut, casa::Bool doTSys);
+
+// Scale data and optionally TSys by values in a Vector
+   void scaleByVector(SDMemTable* pTabOut, const SDMemTable& in,
+                       casa::Bool doAll, const casa::Vector<casa::Float>& factor,
+                       casa::Bool doTSys);
+
+// Convert time String to Epoch
+   casa::MEpoch epochFromString(const casa::String& str, casa::MEpoch::Types timeRef);
+
+// Function to fill Scan Container when averaging in time
+
+  void fillSDC(SDContainer& sc, const casa::Array<casa::Bool>& mask,
+                const casa::Array<casa::Float>& data,
+                const casa::Array<casa::Float>& tSys,
+                casa::Int scanID, casa::Double timeStamp,
+                casa::Double interval, const casa::String& sourceName,
+                const casa::Vector<casa::uInt>& freqID);
+
+// Format EPoch
+   casa::String formatEpoch(const casa::MEpoch& epoch) const; 
+
+// Align in Frequency
+   SDMemTable* frequencyAlign(const SDMemTable& in,
+                              casa::MFrequency::Types system,
+                              const casa::String& timeRef,
+                              const casa::String& method, 
+			       casa::Bool perIF);
+
+// Generate frequency aligners
+   void generateFrequencyAligners(casa::PtrBlock<casa::FrequencyAligner<casa::Float>* >& a,
+                                   const SDDataDesc& dDesc,
+                                   const SDMemTable& in, casa::uInt nChan,
+                                   casa::MFrequency::Types system,
+                                   const casa::MPosition& refPos,
+                                   const casa::MEpoch& refEpoch, 
+				   casa::Bool perFreqID);
+
+// Generate data description table(combines source and freqID);
+   void generateDataDescTable(casa::Matrix<casa::uInt>& ddIdx,
+                               SDDataDesc& dDesc,
+                               casa::uInt nIF,
+                               const SDMemTable& in,
+                               const casa::Table& tabIn,
+                               const casa::ROScalarColumn<casa::String>& srcCol,
+                               const casa::ROArrayColumn<casa::uInt>& fqIDCol,
+                               casa::Bool perFreqID);
+
+// Get row range from SDMemTable state
+  casa::Vector<casa::uInt> getRowRange(const SDMemTable& in) const;
+
+// Is row in the row range ?
+  casa::Bool rowInRange(casa::uInt i, const casa::Vector<casa::uInt>& range) const;
+
+// Set slice to cursor or all axes
+    void setCursorSlice(casa::IPosition& start, casa::IPosition& end, 
+			casa::Bool doAll, const SDMemTable& in) const;
+
+// Function to normalize data when averaging in time
+
+  void normalize(casa::MaskedArray<casa::Float>& data,
+                  const casa::Array<casa::Float>& sumSq,
+                  const casa::Array<casa::Float>& tSysSumSq,
+                  const casa::Array<casa::Float>& nPts,
+                  casa::Double intSum,
+		 WeightType wtType, casa::Int axis, casa::Int nAxes);
+
+// Put the data and mask into the SDContainer
+   void putDataInSDC(SDContainer& sc, const casa::Array<casa::Float>& data,
+		     const casa::Array<casa::Bool>& mask);
+
+// Read ascii file into a Table
+
+  casa::Table readAsciiFile(const casa::String& fileName) const;
+
+}; // class
+
+} // namespace
+
+#endif
Index: tags/asap1/src/SDMathWrapper.cc
===================================================================
--- tags/asap1/src/SDMathWrapper.cc	(revision 819)
+++ tags/asap1/src/SDMathWrapper.cc	(revision 819)
@@ -0,0 +1,322 @@
+//#---------------------------------------------------------------------------
+//# SDMathWrapper.cc: 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:
+//#---------------------------------------------------------------------------
+
+#include "SDMathWrapper.h"
+
+using namespace asap;
+using namespace casa;
+
+
+SDMemTableWrapper SDMathWrapper::quotient(const SDMemTableWrapper& on,
+                                          const SDMemTableWrapper& off,
+                                          Bool preserveContinuum)
+{
+    
+    Bool doTSys = True;
+    return SDMemTableWrapper(sdmath.binaryOperate(on.getCP(), off.getCP(), 
+						  String("QUOTIENT"), 
+						  preserveContinuum, doTSys));
+}
+
+
+SDMemTableWrapper SDMathWrapper::binaryOperate(const SDMemTableWrapper& left,
+                                               const SDMemTableWrapper& right,
+                                               const std::string& op, 
+					       bool doTSys)
+{
+    
+    return SDMemTableWrapper(sdmath.binaryOperate(left.getCP(), right.getCP(), 
+						  String(op), False, 
+						  Bool(doTSys)));
+}
+
+
+void SDMathWrapper::scaleInSitu(SDMemTableWrapper& in, float factor, 
+				bool doAll, bool doTSys)
+{
+  SDMemTable* pIn = in.getPtr();
+  const uInt what = 0;
+  
+  SDMemTable* pOut = sdmath.unaryOperate (*pIn, Float(factor), 
+					Bool(doAll), what, Bool(doTSys));
+  *pIn = *pOut;
+   delete pOut;
+}
+
+SDMemTableWrapper SDMathWrapper::scale(const SDMemTableWrapper& in,
+                                       float factor, bool doAll, bool doTSys)
+{
+  const CountedPtr<SDMemTable>& pIn = in.getCP();
+  const uInt what = 0;
+  
+  return CountedPtr<SDMemTable>(sdmath.unaryOperate(*pIn, Float(factor), Bool(doAll), 
+                                                 what, Bool (doTSys)));
+}
+
+
+void SDMathWrapper::addInSitu(SDMemTableWrapper& in, float offset, bool doAll)
+{
+  SDMemTable* pIn = in.getPtr();
+  const uInt what = 1;
+  
+  Bool doTSys = False;
+  SDMemTable* pOut = sdmath.unaryOperate (*pIn, Float(offset), 
+					Bool(doAll), what, doTSys);
+  *pIn = *pOut;
+   delete pOut;
+}
+
+SDMemTableWrapper SDMathWrapper::add(const SDMemTableWrapper& in,
+                                     float offset, bool doAll)
+{
+  const CountedPtr<SDMemTable>& pIn = in.getCP();
+  const uInt what = 1;
+  
+  Bool doTSys = False;
+  return CountedPtr<SDMemTable>(sdmath.unaryOperate(*pIn, Float(offset),
+						  Bool(doAll), what, doTSys));
+}
+
+
+void SDMathWrapper::smoothInSitu(SDMemTableWrapper& in, 
+				 const std::string& kernel, float width,
+				 bool doAll)
+{
+  SDMemTable* pIn = in.getPtr();
+  
+  SDMemTable* pOut = sdmath.smooth(*pIn, String(kernel), 
+				Float(width), Bool(doAll));
+  *pIn = *pOut;
+   delete pOut;
+}
+
+
+SDMemTableWrapper SDMathWrapper::smooth (const SDMemTableWrapper& in, 
+					 const std::string& kernel, 
+                                         float width, bool doAll)
+{
+  const CountedPtr<SDMemTable>& pIn = in.getCP();
+  
+  return CountedPtr<SDMemTable>(sdmath.smooth(*pIn, String(kernel), 
+					   Float(width), Bool(doAll)));
+}
+
+
+
+void SDMathWrapper::binInSitu(SDMemTableWrapper& in, int width)
+{
+  SDMemTable* pIn = in.getPtr();
+  
+  SDMemTable* pOut = sdmath.bin (*pIn, Int(width));
+  *pIn = *pOut;
+   delete pOut;
+}
+
+SDMemTableWrapper SDMathWrapper::bin (const SDMemTableWrapper& in,
+                                      int width)
+{
+  const CountedPtr<SDMemTable>& pIn = in.getCP();
+  
+  return CountedPtr<SDMemTable>(sdmath.bin(*pIn, Int(width)));
+}
+
+void SDMathWrapper::resampleInSitu(SDMemTableWrapper& in, const std::string& method,
+                                   float width)
+{
+  SDMemTable* pIn = in.getPtr();
+  
+  SDMemTable* pOut = sdmath.resample(*pIn, String(method), Float(width));
+  *pIn = *pOut;
+   delete pOut;
+}
+
+SDMemTableWrapper SDMathWrapper::resample (const SDMemTableWrapper& in,
+                                           const std::string& method, float width)
+{
+  const CountedPtr<SDMemTable>& pIn = in.getCP();
+  
+  return CountedPtr<SDMemTable>(sdmath.resample(*pIn, String(method), Float(width)));
+}
+
+
+void SDMathWrapper::averagePolInSitu(SDMemTableWrapper& in, 
+				     const std::vector<bool>& mask,
+                                     const std::string& weightStr)
+{
+  SDMemTable* pIn = in.getPtr();
+  
+  Vector<Bool> tMask(mask);
+  SDMemTable* pOut = sdmath.averagePol (*pIn, tMask, String(weightStr));
+  *pIn = *pOut;
+   delete pOut;
+}
+
+SDMemTableWrapper SDMathWrapper::averagePol (const SDMemTableWrapper& in,
+					     const std::vector<bool>& mask,
+                                             const std::string& weightStr)
+{
+  const CountedPtr<SDMemTable>& pIn = in.getCP();
+  
+  Vector<Bool> tMask(mask);
+  return CountedPtr<SDMemTable>(sdmath.averagePol(*pIn, tMask, String(weightStr)));
+}
+
+
+std::vector<float> SDMathWrapper::statistic(const SDMemTableWrapper& in,
+                                            const std::vector<bool>& mask, 
+                                            const std::string& which, int row) 
+{
+  
+  Vector<Bool> tMask(mask);
+  return sdmath.statistic(in.getCP(), tMask, String(which), Int(row));
+}
+
+
+void SDMathWrapper::convertFluxInSitu(SDMemTableWrapper& in, float D,
+                                      float eta, float JyPerK, bool doAll)
+{
+  SDMemTable* pIn = in.getPtr();
+  
+  SDMemTable* pOut = sdmath.convertFlux (*pIn, Float(D), Float(eta), Float(JyPerK), Bool(doAll));
+  *pIn = *pOut;
+  delete pOut;
+}
+
+
+SDMemTableWrapper SDMathWrapper::convertFlux(const SDMemTableWrapper& in, float D, float eta, 
+                                             float JyPerK, bool doAll)
+{
+  const CountedPtr<SDMemTable>& pIn = in.getCP();
+  
+  return CountedPtr<SDMemTable>(sdmath.convertFlux(*pIn, Float(D), Float(eta), Float(JyPerK), Bool(doAll)));
+}
+
+
+void SDMathWrapper::gainElevationInSitu(SDMemTableWrapper& in, 
+                                        const std::vector<float>& coeffs,
+                                        const string& fileName,
+                                        const string& method, bool doAll)
+{
+  SDMemTable* pIn = in.getPtr();
+  Vector<Float> tCoeffs(coeffs);
+  
+  SDMemTable* pOut = sdmath.gainElevation(*pIn, tCoeffs, String(fileName), 
+                                       String(method), Bool(doAll));
+  *pIn = *pOut;
+  delete pOut;
+}
+
+
+SDMemTableWrapper SDMathWrapper::gainElevation(const SDMemTableWrapper& in, 
+                                               const std::vector<float>& coeffs,
+                                               const string& fileName, 
+                                               const string& method, bool doAll)
+{
+  const CountedPtr<SDMemTable>& pIn = in.getCP();
+  Vector<Float> tCoeffs(coeffs);
+  
+  return CountedPtr<SDMemTable>(sdmath.gainElevation(*pIn, tCoeffs, String(fileName), 
+                                                  String(method), Bool(doAll)));
+}
+
+void SDMathWrapper::frequencyAlignmentInSitu (SDMemTableWrapper& in, const std::string& refTime,
+                                              const std::string& method, bool perFreqID)
+{
+  SDMemTable* pIn = in.getPtr();
+  
+  SDMemTable* pOut = sdmath.frequencyAlignment(*pIn, String(refTime), String(method),
+                                            Bool(perFreqID));
+  *pIn = *pOut;
+  delete pOut;
+}
+
+
+SDMemTableWrapper SDMathWrapper::frequencyAlignment (const SDMemTableWrapper& in,
+                                                     const std::string& refTime,
+                                                     const std::string& method,
+                                                     bool perFreqID)
+{
+  const CountedPtr<SDMemTable>& pIn = in.getCP();
+  
+  return CountedPtr<SDMemTable>(sdmath.frequencyAlignment(*pIn, String(refTime), 
+                                                       String(method), Bool(perFreqID)));
+}
+
+void SDMathWrapper::rotateXYPhaseInSitu(SDMemTableWrapper& in, float angle, bool doAll)
+{
+  SDMemTable* pIn = in.getPtr();
+  
+  sdmath.rotateXYPhase(*pIn, Float(angle), Bool(doAll));
+}
+
+
+void SDMathWrapper::rotateLinPolPhaseInSitu(SDMemTableWrapper& in, float angle, bool doAll)
+{
+  SDMemTable* pIn = in.getPtr();
+  
+  sdmath.rotateLinPolPhase(*pIn, Float(angle), Bool(doAll));
+}
+
+
+
+
+void SDMathWrapper::opacityInSitu(SDMemTableWrapper& in, float tau, bool doAll)
+{
+  SDMemTable* pIn = in.getPtr();
+  
+  SDMemTable* pOut = sdmath.opacity(*pIn, Float(tau), Bool(doAll));
+  *pIn = *pOut;
+  delete pOut;
+}
+
+SDMemTableWrapper SDMathWrapper::opacity(const SDMemTableWrapper& in, 
+                                         float tau, bool doAll)
+{
+  const CountedPtr<SDMemTable>& pIn = in.getCP();
+  
+  return CountedPtr<SDMemTable>(sdmath.opacity(*pIn, Float(tau), Bool(doAll)));
+}
+
+void SDMathWrapper::frequencySwitchInSitu(SDMemTableWrapper& in)
+{
+  SDMemTable* pIn = in.getPtr();
+  SDMath sdm;
+  SDMemTable* pOut = sdm.frequencySwitch(*pIn);
+  *pIn = *pOut;
+  delete pOut;
+}
+
+SDMemTableWrapper SDMathWrapper::frequencySwitch(const SDMemTableWrapper& in)
+{
+  const CountedPtr<SDMemTable>& pIn = in.getCP();
+  SDMath sdm;
+  return CountedPtr<SDMemTable>(sdm.frequencySwitch(*pIn));
+}
Index: tags/asap1/src/SDMathWrapper.h
===================================================================
--- tags/asap1/src/SDMathWrapper.h	(revision 819)
+++ tags/asap1/src/SDMathWrapper.h	(revision 819)
@@ -0,0 +1,142 @@
+//#---------------------------------------------------------------------------
+//# SDMathWrapper.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 SDMATHWRAPPER_H
+#define SDMATHWRAPPER_H
+
+#include <vector>
+#include <string>
+
+#include <boost/python/tuple.hpp>
+
+#include "SDMemTableWrapper.h"
+#include "SDMath.h"
+
+namespace asap {
+
+namespace SDMathWrapper {
+
+
+  static SDMath sdmath;
+  // Quotient
+  SDMemTableWrapper quotient(const SDMemTableWrapper& on,
+                             const SDMemTableWrapper& off,
+                             casa::Bool preserveContinuum);
+
+  // Simple binary operations
+  SDMemTableWrapper binaryOperate(const SDMemTableWrapper& left,
+				  const SDMemTableWrapper& right,
+				  const std::string& op, bool doTSys);
+  
+  // Multiply
+  void scaleInSitu(SDMemTableWrapper& in, float factor, bool doAll, 
+		   bool doTSys);
+  SDMemTableWrapper scale(const SDMemTableWrapper& in, float factor, 
+			  bool all, bool doTSys);
+  
+  // Add
+  void addInSitu(SDMemTableWrapper& in, float offset, bool doAall);
+  SDMemTableWrapper add(const SDMemTableWrapper& in, float offset, bool all);
+
+// Smooth
+  void smoothInSitu(SDMemTableWrapper& in, const std::string& kernel,
+		     float width, bool doAll);
+  SDMemTableWrapper smooth(const SDMemTableWrapper& in,
+			   const std::string& kernel, float width, bool doAll);
+
+// Bin up
+  void binInSitu(SDMemTableWrapper& in, int width);
+  SDMemTableWrapper bin(const SDMemTableWrapper& in, int width);
+
+// Resample 
+  void resampleInSitu(SDMemTableWrapper& in, const std::string& method, 
+		      float width);
+  SDMemTableWrapper resample(const SDMemTableWrapper& in, 
+			     const std::string& method, 
+                             float width);
+
+// Convert brightness between Jy and K
+  void convertFluxInSitu(SDMemTableWrapper& in, float D, float eta, 
+			 float JyPerK, bool doAll);
+  SDMemTableWrapper convertFlux(const SDMemTableWrapper& in, float D, 
+				float eta, float JyPerK, bool doAll);
+
+// Apply gain elevation correction
+  void gainElevationInSitu (SDMemTableWrapper& in, const std::vector<float>& coeffs,
+                            const std::string& fileName, 
+                            const std::string& method, bool doAll);
+  SDMemTableWrapper gainElevation(const SDMemTableWrapper& in, const std::vector<float>& coeffs,
+                                  const std::string& fileName, 
+                                  const std::string& method, bool doAll);
+
+// Apply frequency alignment
+  void frequencyAlignmentInSitu (SDMemTableWrapper& in, const std::string& refTime,
+                                 const std::string& method, bool perFreqID);
+  SDMemTableWrapper frequencyAlignment(const SDMemTableWrapper& in, const std::string& refTime,
+                                       const std::string& method, bool perFreqID);
+
+// XY Phase rotation
+  void rotateXYPhaseInSitu(SDMemTableWrapper& in, float angle, bool doAll);
+
+// LinPol Phase rotation
+  void rotateLinPolPhaseInSitu(SDMemTableWrapper& in, float angle, bool doAll);
+
+// Apply opacity correction
+  void opacityInSitu(SDMemTableWrapper& in, float tau, bool doAll);
+  SDMemTableWrapper opacity(const SDMemTableWrapper& in, float tau, 
+			    bool doAll);
+
+// Average in time
+  SDMemTableWrapper average(boost::python::tuple tpl,
+			    const std::vector<bool>& mask,
+			    bool scanAv, const std::string& wt);
+
+// Average polarizations
+  void averagePolInSitu(SDMemTableWrapper& in,  const std::vector<bool>& mask,
+                        const std::string& weightStr);
+  SDMemTableWrapper averagePol(const SDMemTableWrapper& in,
+			       const std::vector<bool>& mask,
+                               const std::string& weightStr);
+
+  // Frequency Switching
+  void SDMathWrapper::frequencySwitchInSitu(SDMemTableWrapper& in);
+
+  SDMemTableWrapper 
+  SDMathWrapper::frequencySwitch(const SDMemTableWrapper& in);
+
+  // Statistics
+  std::vector<float> statistic(const SDMemTableWrapper& in,
+                               const std::vector<bool>& mask, 
+                               const std::string& which, int row);
+
+};
+
+} // namespace
+#endif
Index: tags/asap1/src/SDMemTable.cc
===================================================================
--- tags/asap1/src/SDMemTable.cc	(revision 819)
+++ tags/asap1/src/SDMemTable.cc	(revision 819)
@@ -0,0 +1,1881 @@
+//#---------------------------------------------------------------------------
+//# SDMemTable.cc: A MemoryTable container 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 <map>
+
+#include <casa/aips.h>
+#include <casa/iostream.h>
+#include <casa/iomanip.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 <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/ExprNode.h>
+#include <tables/Tables/TableRecord.h>
+#include <measures/Measures/MFrequency.h>
+#include <measures/Measures/MeasTable.h>
+#include <coordinates/Coordinates/CoordinateUtil.h>
+#include <casa/Quanta/MVTime.h>
+#include <casa/Quanta/MVAngle.h>
+
+#include "SDDefs.h"
+#include "SDContainer.h"
+#include "MathUtils.h"
+#include "SDPol.h"
+#include "SDAttr.h"
+
+#include "SDMemTable.h"
+
+using namespace casa;
+using namespace asap;
+
+SDMemTable::SDMemTable() :
+  IFSel_(0),
+  beamSel_(0),
+  polSel_(0)
+{
+  setup();
+  attach();
+}
+
+SDMemTable::SDMemTable(const std::string& name) :
+  IFSel_(0),
+  beamSel_(0),
+  polSel_(0)
+{
+  Table tab(name);
+  Int version;
+  tab.keywordSet().get("VERSION", version);
+  if (version != version_) {
+    throw(AipsError("Unsupported version of ASAP file."));
+  }
+  table_ = tab.copyToMemoryTable("dummy");
+  attach();
+}
+
+SDMemTable::SDMemTable(const SDMemTable& other, Bool clear)
+{
+  table_ = other.table_.copyToMemoryTable(String("dummy"));
+  // clear all rows()
+  if (clear) {
+    table_.removeRow(this->table_.rowNumbers());
+    IFSel_= 0;
+    beamSel_= 0;
+    polSel_= 0;
+  } else {
+    IFSel_= other.IFSel_;
+    beamSel_= other.beamSel_;
+    polSel_= other.polSel_;
+  }
+
+  attach();
+}
+
+SDMemTable::SDMemTable(const Table& tab, const std::string& exprs) :
+  IFSel_(0),
+  beamSel_(0),
+  polSel_(0)
+{
+  Table t = tableCommand(exprs,tab);
+  if (t.nrow() == 0)
+      throw(AipsError("Query unsuccessful."));
+  table_ = t.copyToMemoryTable("dummy");
+  attach();
+  renumber();
+}
+
+SDMemTable::~SDMemTable()
+{
+  //cerr << "goodbye from SDMemTable @ " << this << endl;
+}
+
+SDMemTable SDMemTable::getScan(Int scanID) const
+{
+  String cond("SELECT * from $1 WHERE SCANID == ");
+  cond += String::toString(scanID);
+  return SDMemTable(table_, cond);
+}
+
+SDMemTable &SDMemTable::operator=(const SDMemTable& other)
+{
+  if (this != &other) {
+     IFSel_= other.IFSel_;
+     beamSel_= other.beamSel_;
+     polSel_= other.polSel_;
+     table_ = other.table_.copyToMemoryTable(String("dummy"));
+     attach();
+  }
+  return *this;
+}
+
+SDMemTable SDMemTable::getSource(const std::string& source) const
+{
+  String cond("SELECT * from $1 WHERE SRCNAME == ");
+  cond += source;
+  return SDMemTable(table_, cond);
+}
+
+void SDMemTable::setup()
+{
+  TableDesc td("", "1", TableDesc::Scratch);
+  td.comment() = "A SDMemTable";
+  td.rwKeywordSet().define("VERSION", Int(version_));
+
+  td.addColumn(ScalarColumnDesc<Double>("TIME"));
+  td.addColumn(ScalarColumnDesc<String>("SRCNAME"));
+  td.addColumn(ArrayColumnDesc<Float>("SPECTRA"));
+  td.addColumn(ArrayColumnDesc<uChar>("FLAGTRA"));
+  td.addColumn(ArrayColumnDesc<Float>("TSYS"));
+  td.addColumn(ArrayColumnDesc<Float>("STOKES"));
+  td.addColumn(ScalarColumnDesc<Int>("SCANID"));
+  td.addColumn(ScalarColumnDesc<Double>("INTERVAL"));
+  td.addColumn(ArrayColumnDesc<uInt>("FREQID"));
+  td.addColumn(ArrayColumnDesc<uInt>("RESTFREQID"));
+  td.addColumn(ArrayColumnDesc<Double>("DIRECTION"));
+  td.addColumn(ScalarColumnDesc<String>("FIELDNAME"));
+  td.addColumn(ScalarColumnDesc<String>("TCALTIME"));
+  td.addColumn(ArrayColumnDesc<Float>("TCAL"));
+  td.addColumn(ScalarColumnDesc<Float>("AZIMUTH"));
+  td.addColumn(ScalarColumnDesc<Float>("ELEVATION"));
+  td.addColumn(ScalarColumnDesc<Float>("PARANGLE"));
+  td.addColumn(ScalarColumnDesc<Int>("REFBEAM"));
+  td.addColumn(ArrayColumnDesc<Int>("FITID"));
+
+  // Now create Table SetUp from the description.
+  SetupNewTable aNewTab("dummy", td, Table::New);
+
+  // Bind the Stokes Virtual machine to the STOKES column Because we
+  // don't know how many polarizations will be in the data at this
+  // point, we must bind the Virtual Engine regardless.  The STOKES
+  // column won't be accessed if not appropriate (nPol=4)
+  SDStokesEngine::registerClass();
+  SDStokesEngine stokesEngine(String("STOKES"), String("SPECTRA"));
+  aNewTab.bindColumn("STOKES", stokesEngine);
+
+  // Create Table
+  table_ = Table(aNewTab, Table::Memory, 0);
+  // add subtable
+  TableDesc tdf("", "1", TableDesc::Scratch);
+  tdf.addColumn(ArrayColumnDesc<String>("FUNCTIONS"));
+  tdf.addColumn(ArrayColumnDesc<Int>("COMPONENTS"));
+  tdf.addColumn(ArrayColumnDesc<Double>("PARAMETERS"));
+  tdf.addColumn(ArrayColumnDesc<Bool>("PARMASK"));
+  tdf.addColumn(ArrayColumnDesc<String>("FRAMEINFO"));
+  SetupNewTable fittab("fits", tdf, Table::New);
+  Table fitTable(fittab, Table::Memory);
+  table_.rwKeywordSet().defineTable("FITS", fitTable);
+
+  TableDesc tdh("", "1", TableDesc::Scratch);
+  tdh.addColumn(ScalarColumnDesc<String>("ITEM"));
+  SetupNewTable histtab("hist", tdh, Table::New);
+  Table histTable(histtab, Table::Memory);
+  table_.rwKeywordSet().defineTable("HISTORY", histTable);
+}
+
+void SDMemTable::attach()
+{
+  timeCol_.attach(table_, "TIME");
+  srcnCol_.attach(table_, "SRCNAME");
+  specCol_.attach(table_, "SPECTRA");
+  flagsCol_.attach(table_, "FLAGTRA");
+  tsCol_.attach(table_, "TSYS");
+  stokesCol_.attach(table_, "STOKES");
+  scanCol_.attach(table_, "SCANID");
+  integrCol_.attach(table_, "INTERVAL");
+  freqidCol_.attach(table_, "FREQID");
+  restfreqidCol_.attach(table_, "RESTFREQID");
+  dirCol_.attach(table_, "DIRECTION");
+  fldnCol_.attach(table_, "FIELDNAME");
+  tcaltCol_.attach(table_, "TCALTIME");
+  tcalCol_.attach(table_, "TCAL");
+  azCol_.attach(table_, "AZIMUTH");
+  elCol_.attach(table_, "ELEVATION");
+  paraCol_.attach(table_, "PARANGLE");
+  rbeamCol_.attach(table_, "REFBEAM");
+  fitCol_.attach(table_,"FITID");
+}
+
+
+std::string SDMemTable::getSourceName(Int whichRow) const
+{
+  String name;
+  srcnCol_.get(whichRow, name);
+  return name;
+}
+
+float SDMemTable::getElevation(Int whichRow) const
+{
+  float elevation;
+  elCol_.get(whichRow, elevation);
+  return elevation;
+}
+
+float SDMemTable::getAzimuth(Int whichRow) const
+{
+  float azimuth;
+  azCol_.get(whichRow, azimuth);
+  return azimuth;
+}
+
+float SDMemTable::getParAngle(Int whichRow) const
+{
+  float parangle;
+  paraCol_.get(whichRow, parangle);
+  return parangle;
+}
+
+std::string SDMemTable::getTime(Int whichRow, Bool showDate) const
+{
+  Double tm;
+  if (whichRow > -1) {
+    timeCol_.get(whichRow, tm);
+  } else {
+    table_.keywordSet().get("UTC",tm);
+  }
+  MVTime mvt(tm);
+  if (showDate)
+    mvt.setFormat(MVTime::YMD);
+  else
+    mvt.setFormat(MVTime::TIME);
+  ostringstream oss;
+  oss << mvt;
+  return String(oss);
+}
+
+double SDMemTable::getInterval(Int whichRow) const
+{
+  Double intval;
+  integrCol_.get(whichRow, intval);
+  return intval;
+}
+
+bool SDMemTable::setIF(Int whichIF)
+{
+  if ( whichIF >= 0 && whichIF < nIF()) {
+    IFSel_ = whichIF;
+    return true;
+  }
+  return false;
+}
+
+bool SDMemTable::setBeam(Int whichBeam)
+{
+  if ( whichBeam >= 0 && whichBeam < nBeam()) {
+    beamSel_ = whichBeam;
+    return true;
+  }
+  return false;
+}
+
+bool SDMemTable::setPol(Int whichPol)
+{
+  if ( whichPol >= 0 && whichPol < nPol()) {
+    polSel_ = whichPol;
+    return true;
+  }
+  return false;
+}
+
+void SDMemTable::resetCursor()
+{
+   polSel_ = 0;
+   IFSel_ = 0;
+   beamSel_ = 0;
+}
+
+std::vector<bool> SDMemTable::getMask(Int whichRow) const
+{
+
+  std::vector<bool> mask;
+
+  Array<uChar> arr;
+  flagsCol_.get(whichRow, arr);
+
+  ArrayAccessor<uChar, Axis<asap::BeamAxis> > aa0(arr);
+  aa0.reset(aa0.begin(uInt(beamSel_)));//go to beam
+  ArrayAccessor<uChar, Axis<asap::IFAxis> > aa1(aa0);
+  aa1.reset(aa1.begin(uInt(IFSel_)));// go to IF
+  ArrayAccessor<uChar, Axis<asap::PolAxis> > aa2(aa1);
+  aa2.reset(aa2.begin(uInt(polSel_)));// go to pol
+
+  for (ArrayAccessor<uChar, Axis<asap::ChanAxis> > i(aa2); i != i.end(); ++i) {
+    bool out =!static_cast<bool>(*i);
+    mask.push_back(out);
+  }
+  return mask;
+}
+
+
+
+std::vector<float> SDMemTable::getSpectrum(Int whichRow) const
+{
+  Array<Float> arr;
+  specCol_.get(whichRow, arr);
+  return getFloatSpectrum(arr);
+}
+
+
+int SDMemTable::nStokes() const
+{
+   return stokesCol_.shape(0).nelements();        // All rows same shape
+}
+
+
+std::vector<float> SDMemTable::getStokesSpectrum(Int whichRow,
+                                                 Bool doPol) const
+  //
+  // Gets one STokes parameter depending on cursor polSel location
+  //  doPol=False  : I,Q,U,V
+  //  doPol=True   : I,P,PA,V   ; P = sqrt(Q**2+U**2), PA = 0.5*atan2(Q,U)
+  //
+{
+  AlwaysAssert(asap::nAxes==4,AipsError);
+  if (nPol()!=1 && nPol()!=2 && nPol()!=4) {
+    throw (AipsError("You must have 1,2 or 4 polarizations to get the Stokes parameters"));
+  }
+
+  // For full conversion we are only supporting linears at the moment
+
+  if (nPol() > 2) {
+    String antName;
+    table_.keywordSet().get("AntennaName", antName);
+    Instrument inst = SDAttr::convertInstrument (antName, True);
+    SDAttr sdAtt;
+    if (sdAtt.feedPolType(inst) != LINEAR) {
+      throw(AipsError("Only linear polarizations are supported"));
+    }
+  }
+
+  Array<Float> arr;
+  stokesCol_.get(whichRow, arr);
+
+  if (doPol && (polSel_==1 || polSel_==2)) {       //   Q,U --> P, P.A.
+
+    // Set current cursor location
+
+    const IPosition& shape = arr.shape();
+    IPosition start, end;
+    getCursorSlice(start, end, shape);
+
+    // Get Q and U slices
+
+    Array<Float> Q = SDPolUtil::getStokesSlice(arr,start,end,"Q");
+    Array<Float> U = SDPolUtil::getStokesSlice(arr,start,end,"U");
+
+    // Compute output
+
+    Array<Float> out;
+    if (polSel_==1) {                                        // P
+      out = SDPolUtil::polarizedIntensity(Q,U);
+    } else if (polSel_==2) {                                 // P.A.
+      out = SDPolUtil::positionAngle(Q,U);
+    }
+
+    // Copy to output
+
+    IPosition vecShape(1,shape(asap::ChanAxis));
+    Vector<Float> outV = out.reform(vecShape);
+    std::vector<float> stlout;
+    outV.tovector(stlout);
+    return stlout;
+
+  } else {
+    // Selects at the cursor location
+    return getFloatSpectrum(arr);
+  }
+}
+
+std::string SDMemTable::getPolarizationLabel(Bool linear, Bool stokes,
+                                              Bool linPol, Int polIdx) const
+{
+   uInt idx = polSel_;
+   if (polIdx >=0) idx = polIdx;
+   return SDPolUtil::polarizationLabel(idx, linear, stokes, linPol);
+}
+
+
+
+std::vector<float> SDMemTable::stokesToPolSpectrum(Int whichRow,
+                                                   Bool toLinear,
+                                                   Int polIdx) const
+//
+// polIdx
+//   0:3 -> RR,LL,Real(RL),Imag(RL)
+//          XX,YY,Real(XY),Image(XY)
+//
+// Gets only
+//  RR = I + V
+//  LL = I - V
+// at the moment
+//
+{
+  AlwaysAssert(asap::nAxes==4,AipsError);
+  if (nStokes()!=4) {
+     throw (AipsError("You must have 4 Stokes to convert to linear or circular"));
+  }
+//
+  Array<Float> arr, out;
+  stokesCol_.get(whichRow, arr);
+
+// Set current cursor location
+
+  const IPosition& shape = arr.shape();
+  IPosition start, end;
+  getCursorSlice(start, end, shape);
+
+// Get the slice
+
+  if (toLinear) {
+     throw(AipsError("Conversion to linears not yet supported"));
+  } else {
+    uInt selection = polSel_;
+    if (polIdx > -1) selection = polIdx;
+    Bool doRR = (selection==0);
+    if (selection>1) {
+      throw(AipsError("Only conversion to RR & LL is currently supported"));
+    }
+
+    // Get I and V slices
+    Array<Float> I = SDPolUtil::getStokesSlice(arr,start,end,"I");
+    Array<Float> V = SDPolUtil::getStokesSlice(arr,start,end,"V");
+
+    // Compute output
+    out = SDPolUtil::circularPolarizationFromStokes(I, V, doRR);
+  }
+
+  // Copy to output
+   IPosition vecShape(1,shape(asap::ChanAxis));
+   Vector<Float> outV = out.reform(vecShape);
+   std::vector<float> stlout;
+   outV.tovector(stlout);
+//
+   return stlout;
+}
+
+
+
+
+Array<Float> SDMemTable::getStokesSpectrum(Int whichRow, Int iBeam, Int iIF) const
+{
+
+// Get data
+
+  Array<Float> arr;
+  stokesCol_.get(whichRow, arr);
+
+// Set current cursor location and overwrite polarization axis
+
+  const IPosition& shape = arr.shape();
+  IPosition start(shape.nelements(),0);
+  IPosition end(shape-1);
+  if (iBeam!=-1) {
+     start(asap::BeamAxis) = iBeam;
+     end(asap::BeamAxis) = iBeam;
+  }
+  if (iIF!=-1) {
+     start(asap::IFAxis) = iIF;
+     end(asap::IFAxis) = iIF;
+  }
+
+// Get slice
+
+  return arr(start,end);
+}
+
+
+std::vector<string> SDMemTable::getCoordInfo() const
+{
+  String un;
+  Table t = table_.keywordSet().asTable("FREQUENCIES");
+  String sunit;
+  t.keywordSet().get("UNIT",sunit);
+  String dpl;
+  t.keywordSet().get("DOPPLER",dpl);
+  if (dpl == "") dpl = "RADIO";
+  String rfrm;
+  t.keywordSet().get("REFFRAME",rfrm);
+  std::vector<string> inf;
+  inf.push_back(sunit);
+  inf.push_back(rfrm);
+  inf.push_back(dpl);
+  t.keywordSet().get("BASEREFFRAME",rfrm);
+  inf.push_back(rfrm);
+  return inf;
+}
+
+void SDMemTable::setCoordInfo(std::vector<string> theinfo)
+{
+  std::vector<string>::iterator it;
+  String un,rfrm, brfrm,dpl;
+  un = theinfo[0];              // Abcissa unit
+  rfrm = theinfo[1];            // Active (or conversion) frame
+  dpl = theinfo[2];             // Doppler
+  brfrm = theinfo[3];           // Base frame
+  Table t = table_.rwKeywordSet().asTable("FREQUENCIES");
+
+  Vector<Double> rstf;
+  t.keywordSet().get("RESTFREQS",rstf);
+
+  Bool canDo = True;
+  Unit u1("km/s");Unit u2("Hz");
+  if (Unit(un) == u1) {
+    Vector<Double> rstf;
+    t.keywordSet().get("RESTFREQS",rstf);
+    if (rstf.nelements() == 0) {
+        throw(AipsError("Can't set unit to km/s if no restfrequencies are specified"));
+    }
+  } else if (Unit(un) != u2 && un != "") {
+        throw(AipsError("Unit not conformant with Spectral Coordinates"));
+  }
+  t.rwKeywordSet().define("UNIT", un);
+
+  MFrequency::Types mdr;
+  if (!MFrequency::getType(mdr, rfrm)) {
+
+    Int a,b;const uInt* c;
+    const String* valid = MFrequency::allMyTypes(a, b, c);
+    String pfix = "Please specify a legal frame type. Types are\n";
+    throw(AipsError(pfix+(*valid)));
+  } else {
+    t.rwKeywordSet().define("REFFRAME",rfrm);
+  }
+
+  MDoppler::Types dtype;
+  dpl.upcase();
+  if (!MDoppler::getType(dtype, dpl)) {
+    throw(AipsError("Doppler type unknown"));
+  } else {
+    t.rwKeywordSet().define("DOPPLER",dpl);
+  }
+
+  if (!MFrequency::getType(mdr, brfrm)) {
+     Int a,b;const uInt* c;
+     const String* valid = MFrequency::allMyTypes(a, b, c);
+     String pfix = "Please specify a legal frame type. Types are\n";
+     throw(AipsError(pfix+(*valid)));
+   } else {
+    t.rwKeywordSet().define("BASEREFFRAME",brfrm);
+   }
+}
+
+
+std::vector<double> SDMemTable::getAbcissa(Int whichRow) const
+{
+  std::vector<double> abc(nChan());
+
+  // Get header units keyword
+  Table t = table_.keywordSet().asTable("FREQUENCIES");
+  String sunit;
+  t.keywordSet().get("UNIT",sunit);
+  if (sunit == "") sunit = "pixel";
+  Unit u(sunit);
+
+  // Easy if just wanting pixels
+  if (sunit==String("pixel")) {
+    // assume channels/pixels
+    std::vector<double>::iterator it;
+    uInt i=0;
+    for (it = abc.begin(); it != abc.end(); ++it) {
+      (*it) = Double(i++);
+    }
+    return abc;
+  }
+
+  // Continue with km/s or Hz.  Get FreqIDs
+  Vector<uInt> freqIDs;
+  freqidCol_.get(whichRow, freqIDs);
+  uInt freqID = freqIDs(IFSel_);
+  restfreqidCol_.get(whichRow, freqIDs);
+  uInt restFreqID = freqIDs(IFSel_);
+
+  // Get SpectralCoordinate, set reference frame conversion,
+  // velocity conversion, and rest freq state
+
+  SpectralCoordinate spc = getSpectralCoordinate(freqID, restFreqID, whichRow);
+  Vector<Double> pixel(nChan());
+  indgen(pixel);
+
+  if (u == Unit("km/s")) {
+     Vector<Double> world;
+     spc.pixelToVelocity(world,pixel);
+     std::vector<double>::iterator it;
+     uInt i = 0;
+     for (it = abc.begin(); it != abc.end(); ++it) {
+       (*it) = world[i];
+       i++;
+     }
+  } else if (u == Unit("Hz")) {
+
+    // Set world axis units
+    Vector<String> wau(1); wau = u.getName();
+    spc.setWorldAxisUnits(wau);
+
+    std::vector<double>::iterator it;
+    Double tmp;
+    uInt i = 0;
+    for (it = abc.begin(); it != abc.end(); ++it) {
+      spc.toWorld(tmp,pixel[i]);
+      (*it) = tmp;
+      i++;
+    }
+  }
+  return abc;
+}
+
+std::string SDMemTable::getAbcissaString(Int whichRow) const
+{
+  Table t = table_.keywordSet().asTable("FREQUENCIES");
+
+  String sunit;
+  t.keywordSet().get("UNIT",sunit);
+  if (sunit == "") sunit = "pixel";
+  Unit u(sunit);
+
+  Vector<uInt> freqIDs;
+  freqidCol_.get(whichRow, freqIDs);
+  uInt freqID = freqIDs(IFSel_);
+  restfreqidCol_.get(whichRow, freqIDs);
+  uInt restFreqID = freqIDs(IFSel_);
+
+  // Get SpectralCoordinate, with frame, velocity, rest freq state set
+  SpectralCoordinate spc = getSpectralCoordinate(freqID, restFreqID, whichRow);
+
+  String s = "Channel";
+  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 SDMemTable::setSpectrum(std::vector<float> spectrum, int whichRow)
+{
+  Array<Float> arr;
+  specCol_.get(whichRow, arr);
+  if (spectrum.size() != arr.shape()(asap::ChanAxis)) {
+    throw(AipsError("Attempting to set spectrum with incorrect length."));
+  }
+
+  // Setup accessors
+  ArrayAccessor<Float, Axis<asap::BeamAxis> > aa0(arr);
+  aa0.reset(aa0.begin(uInt(beamSel_)));                   // Beam selection
+  ArrayAccessor<Float, Axis<asap::IFAxis> > aa1(aa0);
+  aa1.reset(aa1.begin(uInt(IFSel_)));                     // IF selection
+  ArrayAccessor<Float, Axis<asap::PolAxis> > aa2(aa1);
+  aa2.reset(aa2.begin(uInt(polSel_)));                    // Pol selection
+
+  // Iterate
+  std::vector<float>::iterator it = spectrum.begin();
+  for (ArrayAccessor<Float, Axis<asap::ChanAxis> > i(aa2); i != i.end(); ++i) {
+    (*i) = Float(*it);
+    it++;
+  }
+  specCol_.put(whichRow, arr);
+}
+
+void SDMemTable::getSpectrum(Vector<Float>& spectrum, Int whichRow) const
+{
+  Array<Float> arr;
+  specCol_.get(whichRow, arr);
+
+  // Iterate and extract
+  spectrum.resize(arr.shape()(3));
+  ArrayAccessor<Float, Axis<asap::BeamAxis> > aa0(arr);
+  aa0.reset(aa0.begin(uInt(beamSel_)));//go to beam
+  ArrayAccessor<Float, Axis<asap::IFAxis> > aa1(aa0);
+  aa1.reset(aa1.begin(uInt(IFSel_)));// go to IF
+  ArrayAccessor<Float, Axis<asap::PolAxis> > aa2(aa1);
+  aa2.reset(aa2.begin(uInt(polSel_)));// go to pol
+
+  ArrayAccessor<Float, Axis<asap::BeamAxis> > va(spectrum);
+  for (ArrayAccessor<Float, Axis<asap::ChanAxis> > i(aa2); i != i.end(); ++i) {
+    (*va) = (*i);
+    va++;
+  }
+}
+
+
+/*
+void SDMemTable::getMask(Vector<Bool>& mask, Int whichRow) const {
+  Array<uChar> arr;
+  flagsCol_.get(whichRow, arr);
+  mask.resize(arr.shape()(3));
+
+  ArrayAccessor<uChar, Axis<asap::BeamAxis> > aa0(arr);
+  aa0.reset(aa0.begin(uInt(beamSel_)));//go to beam
+  ArrayAccessor<uChar, Axis<asap::IFAxis> > aa1(aa0);
+  aa1.reset(aa1.begin(uInt(IFSel_)));// go to IF
+  ArrayAccessor<uChar, Axis<asap::PolAxis> > aa2(aa1);
+  aa2.reset(aa2.begin(uInt(polSel_)));// go to pol
+
+  Bool useUserMask = ( chanMask_.size() == arr.shape()(3) );
+
+  ArrayAccessor<Bool, Axis<asap::BeamAxis> > va(mask);
+  std::vector<bool> tmp;
+  tmp = chanMask_; // WHY the fxxx do I have to make a copy here. The
+                   // iterator should work on chanMask_??
+  std::vector<bool>::iterator miter;
+  miter = tmp.begin();
+
+  for (ArrayAccessor<uChar, Axis<asap::ChanAxis> > i(aa2); i != i.end(); ++i) {
+    bool out =!static_cast<bool>(*i);
+    if (useUserMask) {
+      out = out && (*miter);
+      miter++;
+    }
+    (*va) = out;
+    va++;
+  }
+}
+*/
+
+MaskedArray<Float> SDMemTable::rowAsMaskedArray(uInt whichRow,
+                                                Bool toStokes) const
+{
+  // Get flags
+  Array<uChar> farr;
+  flagsCol_.get(whichRow, farr);
+
+  // Get data and convert mask to Bool
+  Array<Float> arr;
+  Array<Bool> mask;
+  if (toStokes) {
+     stokesCol_.get(whichRow, arr);
+
+     Array<Bool> tMask(farr.shape());
+     convertArray(tMask, farr);
+     mask = SDPolUtil::stokesData (tMask, True);
+  } else {
+     specCol_.get(whichRow, arr);
+     mask.resize(farr.shape());
+     convertArray(mask, farr);
+  }
+
+  return MaskedArray<Float>(arr,!mask);
+}
+
+Float SDMemTable::getTsys(Int whichRow) const
+{
+  Array<Float> arr;
+  tsCol_.get(whichRow, arr);
+  Float out;
+
+  IPosition ip(arr.shape());
+  ip(asap::BeamAxis) = beamSel_;
+  ip(asap::IFAxis) = IFSel_;
+  ip(asap::PolAxis) = polSel_;
+  ip(asap::ChanAxis)=0;               // First channel only
+
+  out = arr(ip);
+  return out;
+}
+
+MDirection SDMemTable::getDirection(Int whichRow, Bool refBeam) const
+{
+  MDirection::Types mdr = getDirectionReference();
+  Array<Double> posit;
+  dirCol_.get(whichRow,posit);
+  Vector<Double> wpos(2);
+  Int rb;
+  rbeamCol_.get(whichRow,rb);
+  wpos[0] = posit(IPosition(2,beamSel_,0));
+  wpos[1] = posit(IPosition(2,beamSel_,1));
+  if (refBeam && rb > -1) {  // use refBeam instead if it exists
+    wpos[0] = posit(IPosition(2,rb,0));
+    wpos[1] = posit(IPosition(2,rb,1));
+  }
+
+  Quantum<Double> lon(wpos[0],Unit(String("rad")));
+  Quantum<Double> lat(wpos[1],Unit(String("rad")));
+  return MDirection(lon, lat, mdr);
+}
+
+MEpoch SDMemTable::getEpoch(Int whichRow) const
+{
+  MEpoch::Types met = getTimeReference();
+
+  Double obstime;
+  timeCol_.get(whichRow,obstime);
+  MVEpoch tm2(Quantum<Double>(obstime, Unit(String("d"))));
+  return MEpoch(tm2, met);
+}
+
+MPosition SDMemTable::getAntennaPosition () const
+{
+  Vector<Double> antpos;
+  table_.keywordSet().get("AntennaPosition", antpos);
+  MVPosition mvpos(antpos(0),antpos(1),antpos(2));
+  return MPosition(mvpos);
+}
+
+
+SpectralCoordinate SDMemTable::getSpectralCoordinate(uInt freqID) const
+{
+
+  Table t = table_.keywordSet().asTable("FREQUENCIES");
+  if (freqID> t.nrow() ) {
+    throw(AipsError("SDMemTable::getSpectralCoordinate - freqID out of range"));
+  }
+
+  Double rp,rv,inc;
+  String rf;
+  ROScalarColumn<Double> rpc(t, "REFPIX");
+  ROScalarColumn<Double> rvc(t, "REFVAL");
+  ROScalarColumn<Double> incc(t, "INCREMENT");
+  t.keywordSet().get("BASEREFFRAME",rf);
+
+  // Create SpectralCoordinate (units Hz)
+  MFrequency::Types mft;
+  if (!MFrequency::getType(mft, rf)) {
+    ostringstream oss;
+    pushLog("WARNING: Frequency type unknown assuming TOPO");
+    mft = MFrequency::TOPO;
+  }
+  rpc.get(freqID, rp);
+  rvc.get(freqID, rv);
+  incc.get(freqID, inc);
+
+  SpectralCoordinate spec(mft,rv,inc,rp);
+  return spec;
+}
+
+
+SpectralCoordinate SDMemTable::getSpectralCoordinate(uInt freqID,
+                                                     uInt restFreqID,
+                                                     uInt whichRow) const
+{
+
+  // Create basic SC
+  SpectralCoordinate spec = getSpectralCoordinate (freqID);
+
+  Table t = table_.keywordSet().asTable("FREQUENCIES");
+
+  // Set rest frequency
+  Vector<Double> restFreqIDs;
+  t.keywordSet().get("RESTFREQS",restFreqIDs);
+  if (restFreqID < restFreqIDs.nelements()) {    // Should always be true
+    spec.setRestFrequency(restFreqIDs(restFreqID),True);
+  }
+
+  // Set up frame conversion layer
+  String frm;
+  t.keywordSet().get("REFFRAME",frm);
+  if (frm == "") frm = "TOPO";
+  MFrequency::Types mtype;
+  if (!MFrequency::getType(mtype, frm)) {
+    // Should never happen
+    pushLog("WARNING: Frequency type unknown assuming TOPO");
+    mtype = MFrequency::TOPO;
+  }
+
+  // Set reference frame conversion  (requires row)
+  MDirection dir = getDirection(whichRow);
+  MEpoch epoch = getEpoch(whichRow);
+  MPosition pos = getAntennaPosition();
+
+  if (!spec.setReferenceConversion(mtype,epoch,pos,dir)) {
+    throw(AipsError("Couldn't convert frequency frame."));
+  }
+
+  // Now velocity conversion if appropriate
+  String unitStr;
+  t.keywordSet().get("UNIT",unitStr);
+
+  String dpl;
+  t.keywordSet().get("DOPPLER",dpl);
+  MDoppler::Types dtype;
+  MDoppler::getType(dtype, dpl);
+
+  // Only set velocity unit if non-blank and non-Hz
+  if (!unitStr.empty()) {
+     Unit unitU(unitStr);
+     if (unitU==Unit("Hz")) {
+     } else {
+        spec.setVelocity(unitStr, dtype);
+     }
+  }
+
+  return spec;
+}
+
+
+Bool SDMemTable::setCoordinate(const SpectralCoordinate& speccord,
+                               uInt freqID) {
+  Table t = table_.rwKeywordSet().asTable("FREQUENCIES");
+  if (freqID > t.nrow() ) {
+    throw(AipsError("SDMemTable::setCoordinate - coord no out of range"));
+  }
+  ScalarColumn<Double> rpc(t, "REFPIX");
+  ScalarColumn<Double> rvc(t, "REFVAL");
+  ScalarColumn<Double> incc(t, "INCREMENT");
+
+  rpc.put(freqID, speccord.referencePixel()[0]);
+  rvc.put(freqID, speccord.referenceValue()[0]);
+  incc.put(freqID, speccord.increment()[0]);
+
+  return True;
+}
+
+Int SDMemTable::nCoordinates() const
+{
+  return table_.keywordSet().asTable("FREQUENCIES").nrow();
+}
+
+
+std::vector<double> SDMemTable::getRestFreqs() const
+{
+  Table t = table_.keywordSet().asTable("FREQUENCIES");
+  Vector<Double> tvec;
+  t.keywordSet().get("RESTFREQS",tvec);
+  std::vector<double> stlout;
+  tvec.tovector(stlout);
+  return stlout;
+}
+
+bool SDMemTable::putSDFreqTable(const SDFrequencyTable& sdft)
+{
+  TableDesc td("", "1", TableDesc::Scratch);
+  td.addColumn(ScalarColumnDesc<Double>("REFPIX"));
+  td.addColumn(ScalarColumnDesc<Double>("REFVAL"));
+  td.addColumn(ScalarColumnDesc<Double>("INCREMENT"));
+  SetupNewTable aNewTab("freqs", td, Table::New);
+  Table aTable (aNewTab, Table::Memory, sdft.length());
+  ScalarColumn<Double> sc0(aTable, "REFPIX");
+  ScalarColumn<Double> sc1(aTable, "REFVAL");
+  ScalarColumn<Double> sc2(aTable, "INCREMENT");
+  for (uInt i=0; i < sdft.length(); ++i) {
+    sc0.put(i,sdft.referencePixel(i));
+    sc1.put(i,sdft.referenceValue(i));
+    sc2.put(i,sdft.increment(i));
+  }
+  String rf = sdft.refFrame();
+  if (rf.contains("TOPO")) rf = "TOPO";
+  String brf = sdft.baseRefFrame();
+  if (brf.contains("TOPO")) brf = "TOPO";
+
+  aTable.rwKeywordSet().define("BASEREFFRAME", brf);
+  aTable.rwKeywordSet().define("REFFRAME", rf);
+  aTable.rwKeywordSet().define("EQUINOX", sdft.equinox());
+  aTable.rwKeywordSet().define("UNIT", sdft.unit());
+  aTable.rwKeywordSet().define("DOPPLER", String("RADIO"));
+  Vector<Double> rfvec;
+  String rfunit;
+  sdft.restFrequencies(rfvec,rfunit);
+  Quantum<Vector<Double> > q(rfvec, rfunit);
+  rfvec.resize();
+  rfvec = q.getValue("Hz");
+  aTable.rwKeywordSet().define("RESTFREQS", rfvec);
+  table_.rwKeywordSet().defineTable("FREQUENCIES", aTable);
+  return true;
+}
+
+bool SDMemTable::putSDFitTable(const SDFitTable& sdft)
+{
+  TableDesc td("", "1", TableDesc::Scratch);
+  td.addColumn(ArrayColumnDesc<String>("FUNCTIONS"));
+  td.addColumn(ArrayColumnDesc<Int>("COMPONENTS"));
+  td.addColumn(ArrayColumnDesc<Double>("PARAMETERS"));
+  td.addColumn(ArrayColumnDesc<Bool>("PARMASK"));
+  td.addColumn(ArrayColumnDesc<String>("FRAMEINFO"));
+  SetupNewTable aNewTab("fits", td, Table::New);
+  Table aTable(aNewTab, Table::Memory);
+  ArrayColumn<String> sc0(aTable, "FUNCTIONS");
+  ArrayColumn<Int> sc1(aTable, "COMPONENTS");
+  ArrayColumn<Double> sc2(aTable, "PARAMETERS");
+  ArrayColumn<Bool> sc3(aTable, "PARMASK");
+  ArrayColumn<String> sc4(aTable, "FRAMEINFO");
+  for (uInt i; i<sdft.length(); ++i) {
+    const Vector<Double>& parms = sdft.getParameters(i);
+    const Vector<Bool>& parmask = sdft.getParameterMask(i);
+    const Vector<String>& funcs = sdft.getFunctions(i);
+    const Vector<Int>& comps = sdft.getComponents(i);
+    const Vector<String>& finfo = sdft.getFrameInfo(i);
+    sc0.put(i,funcs);
+    sc1.put(i,comps);
+    sc3.put(i,parmask);
+    sc2.put(i,parms);
+    sc4.put(i,finfo);
+  }
+  table_.rwKeywordSet().defineTable("FITS", aTable);
+  return true;
+}
+
+SDFitTable SDMemTable::getSDFitTable() const
+{
+  const Table& t = table_.keywordSet().asTable("FITS");
+  Vector<Double> parms;
+  Vector<Bool> parmask;
+  Vector<String> funcs;
+  Vector<String> finfo;
+  Vector<Int> comps;
+  ROArrayColumn<Double> parmsCol(t, "PARAMETERS");
+  ROArrayColumn<Bool> parmaskCol(t, "PARMASK");
+  ROArrayColumn<Int> compsCol(t, "COMPONENTS");
+  ROArrayColumn<String> funcsCol(t, "FUNCTIONS");
+  ROArrayColumn<String> finfoCol(t, "FRAMEINFO");
+  uInt n = t.nrow();
+  SDFitTable sdft;
+  for (uInt i=0; i<n; ++i) {
+    parmaskCol.get(i, parmask);
+    parmsCol.get(i, parms);
+    funcsCol.get(i, funcs);
+    compsCol.get(i, comps);
+    finfoCol.get(i, finfo);
+    sdft.addFit(parms, parmask, funcs, comps, finfo);
+  }
+  return sdft;
+}
+
+SDFitTable SDMemTable::getSDFitTable(uInt whichRow) const {
+  const Table& t = table_.keywordSet().asTable("FITS");
+  if (t.nrow() == 0 || whichRow >= t.nrow()) return SDFitTable();
+  Array<Int> fitid;
+  fitCol_.get(whichRow, fitid);
+  if (fitid.nelements() == 0) return SDFitTable();
+
+  IPosition shp = fitid.shape();
+  IPosition start(4, beamSel_, IFSel_, polSel_,0);
+  IPosition end(4, beamSel_, IFSel_, polSel_, shp[3]-1);
+
+  // reform the output array slice to be of dim=1
+  Vector<Int> tmp = (fitid(start, end)).reform(IPosition(1,shp[3]));
+
+  Vector<Double> parms;
+  Vector<Bool> parmask;
+  Vector<String> funcs;
+  Vector<String> finfo;
+  Vector<Int> comps;
+  ROArrayColumn<Double> parmsCol(t, "PARAMETERS");
+  ROArrayColumn<Bool> parmaskCol(t, "PARMASK");
+  ROArrayColumn<Int> compsCol(t, "COMPONENTS");
+  ROArrayColumn<String> funcsCol(t, "FUNCTIONS");
+  ROArrayColumn<String> finfoCol(t, "FRAMEINFO");
+  SDFitTable sdft;
+  Int k=-1;
+  for (uInt i=0; i< tmp.nelements(); ++i) {
+    k = tmp[i];
+    if ( k > -1 && k < t.nrow() ) {
+      parms.resize();
+      parmsCol.get(k, parms);
+      parmask.resize();
+      parmaskCol.get(k, parmask);
+      funcs.resize();
+      funcsCol.get(k, funcs);
+      comps.resize();
+      compsCol.get(k, comps);
+      finfo.resize();
+      finfoCol.get(k, finfo);
+      sdft.addFit(parms, parmask, funcs, comps, finfo);
+    }
+  }
+  return sdft;
+}
+
+void SDMemTable::addFit(uInt whichRow,
+                        const Vector<Double>& p, const Vector<Bool>& m,
+                        const Vector<String>& f, const Vector<Int>& c)
+{
+  if (whichRow >= nRow()) {
+    throw(AipsError("Specified row out of range"));
+  }
+  Table t = table_.keywordSet().asTable("FITS");
+  uInt nrow = t.nrow();
+  t.addRow();
+  ArrayColumn<Double> parmsCol(t, "PARAMETERS");
+  ArrayColumn<Bool> parmaskCol(t, "PARMASK");
+  ArrayColumn<Int> compsCol(t, "COMPONENTS");
+  ArrayColumn<String> funcsCol(t, "FUNCTIONS");
+  ArrayColumn<String> finfoCol(t, "FRAMEINFO");
+  parmsCol.put(nrow, p);
+  parmaskCol.put(nrow, m);
+  compsCol.put(nrow, c);
+  funcsCol.put(nrow, f);
+  Vector<String> fi = mathutil::toVectorString(getCoordInfo());
+  finfoCol.put(nrow, fi);
+
+  Array<Int> fitarr;
+  fitCol_.get(whichRow, fitarr);
+
+  Array<Int> newarr;               // The new Array containing the fitid
+  Int pos =-1;                     // The fitid position in the array
+  if ( fitarr.nelements() == 0 ) { // no fits at all in this row
+    Array<Int> arr(IPosition(4,nBeam(),nIF(),nPol(),1));
+    arr = -1;
+    newarr.reference(arr);
+    pos = 0;
+  } else {
+    IPosition shp = fitarr.shape();
+    IPosition start(4, beamSel_, IFSel_, polSel_,0);
+    IPosition end(4, beamSel_, IFSel_, polSel_, shp[3]-1);
+    // reform the output array slice to be of dim=1
+    Array<Int> tmp = (fitarr(start, end)).reform(IPosition(1,shp[3]));
+    const Vector<Int>& fits = tmp;
+    VectorSTLIterator<Int> it(fits);
+    Int i = 0;
+    while (it != fits.end()) {
+      if (*it == -1) {
+        pos = i;
+        break;
+      }
+      ++i;
+      ++it;
+    };
+  }
+  if (pos == -1) {
+      mathutil::extendLastArrayAxis(newarr,fitarr, -1);
+      pos = fitarr.shape()[3];     // the new element position
+  } else {
+    if (fitarr.nelements() > 0)
+      newarr = fitarr;
+  }
+  newarr(IPosition(4, beamSel_, IFSel_, polSel_, pos)) = Int(nrow);
+  fitCol_.put(whichRow, newarr);
+
+}
+
+SDFrequencyTable SDMemTable::getSDFreqTable() const
+{
+  const Table& t = table_.keywordSet().asTable("FREQUENCIES");
+  SDFrequencyTable sdft;
+
+  // Add refpix/refval/incr.  What are the units ? Hz I suppose
+  // but it's nowhere described...
+  Vector<Double> refPix, refVal, incr;
+  ScalarColumn<Double> refPixCol(t, "REFPIX");
+  ScalarColumn<Double> refValCol(t, "REFVAL");
+  ScalarColumn<Double> incrCol(t, "INCREMENT");
+  refPix = refPixCol.getColumn();
+  refVal = refValCol.getColumn();
+  incr = incrCol.getColumn();
+
+  uInt n = refPix.nelements();
+  for (uInt i=0; i<n; i++) {
+     sdft.addFrequency(refPix[i], refVal[i], incr[i]);
+  }
+
+  // Frequency reference frame.  I don't know if this
+  // is the correct frame.  It might be 'REFFRAME'
+  // rather than 'BASEREFFRAME' ?
+  String frame;
+  t.keywordSet().get("REFFRAME",frame);
+  sdft.setRefFrame(frame);
+  t.keywordSet().get("BASEREFFRAME",frame);
+  sdft.setBaseRefFrame(frame);
+
+  // Equinox
+  Float equinox;
+  t.keywordSet().get("EQUINOX", equinox);
+  sdft.setEquinox(equinox);
+
+  String unit;
+  t.keywordSet().get("UNIT", unit);
+  sdft.setUnit(unit);
+
+  // Rest Frequency
+  Vector<Double> restFreqs;
+  t.keywordSet().get("RESTFREQS", restFreqs);
+  for (uInt i=0; i<restFreqs.nelements(); i++) {
+     sdft.addRestFrequency(restFreqs[i]);
+  }
+  sdft.setRestFrequencyUnit(String("Hz"));
+
+  return sdft;
+}
+
+bool SDMemTable::putSDContainer(const SDContainer& sdc)
+{
+  uInt rno = table_.nrow();
+  table_.addRow();
+
+  timeCol_.put(rno, sdc.timestamp);
+  srcnCol_.put(rno, sdc.sourcename);
+  fldnCol_.put(rno, sdc.fieldname);
+  specCol_.put(rno, sdc.getSpectrum());
+  flagsCol_.put(rno, sdc.getFlags());
+  tsCol_.put(rno, sdc.getTsys());
+  scanCol_.put(rno, sdc.scanid);
+  integrCol_.put(rno, sdc.interval);
+  freqidCol_.put(rno, sdc.getFreqMap());
+  restfreqidCol_.put(rno, sdc.getRestFreqMap());
+  dirCol_.put(rno, sdc.getDirection());
+  rbeamCol_.put(rno, sdc.refbeam);
+  tcalCol_.put(rno, sdc.tcal);
+  tcaltCol_.put(rno, sdc.tcaltime);
+  azCol_.put(rno, sdc.azimuth);
+  elCol_.put(rno, sdc.elevation);
+  paraCol_.put(rno, sdc.parangle);
+  fitCol_.put(rno, sdc.getFitMap());
+  return true;
+}
+
+SDContainer SDMemTable::getSDContainer(uInt whichRow) const
+{
+  SDContainer sdc(nBeam(),nIF(),nPol(),nChan());
+  timeCol_.get(whichRow, sdc.timestamp);
+  srcnCol_.get(whichRow, sdc.sourcename);
+  integrCol_.get(whichRow, sdc.interval);
+  scanCol_.get(whichRow, sdc.scanid);
+  fldnCol_.get(whichRow, sdc.fieldname);
+  rbeamCol_.get(whichRow, sdc.refbeam);
+  azCol_.get(whichRow, sdc.azimuth);
+  elCol_.get(whichRow, sdc.elevation);
+  paraCol_.get(whichRow, sdc.parangle);
+  Vector<Float> tc;
+  tcalCol_.get(whichRow, tc);
+  sdc.tcal[0] = tc[0];sdc.tcal[1] = tc[1];
+  tcaltCol_.get(whichRow, sdc.tcaltime);
+
+  Array<Float> spectrum;
+  Array<Float> tsys;
+  Array<uChar> flagtrum;
+  Vector<uInt> fmap;
+  Array<Double> direction;
+  Array<Int> fits;
+
+  specCol_.get(whichRow, spectrum);
+  sdc.putSpectrum(spectrum);
+  flagsCol_.get(whichRow, flagtrum);
+  sdc.putFlags(flagtrum);
+  tsCol_.get(whichRow, tsys);
+  sdc.putTsys(tsys);
+  freqidCol_.get(whichRow, fmap);
+  sdc.putFreqMap(fmap);
+  restfreqidCol_.get(whichRow, fmap);
+  sdc.putRestFreqMap(fmap);
+  dirCol_.get(whichRow, direction);
+  sdc.putDirection(direction);
+  fitCol_.get(whichRow, fits);
+  sdc.putFitMap(fits);
+  return sdc;
+}
+
+bool SDMemTable::putSDHeader(const SDHeader& 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);
+  return true;
+}
+
+SDHeader SDMemTable::getSDHeader() const
+{
+  SDHeader 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);
+  return sdh;
+}
+void SDMemTable::makePersistent(const std::string& filename)
+{
+  table_.deepCopy(filename,Table::New);
+
+}
+
+Int SDMemTable::nScan() const {
+  Int n = 0;
+  Int previous = -1;Int current=0;
+  for (uInt i=0; i< scanCol_.nrow();i++) {
+    scanCol_.getScalar(i,current);
+    if (previous != current) {
+      previous = current;
+      n++;
+    }
+  }
+  return n;
+}
+
+String SDMemTable::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);
+  }
+};
+
+String SDMemTable::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);
+  MVAngle mvLat(t[1]);
+  String sLat = mvLat.string(MVAngle::ANGLE+MVAngle::DIG2,prec);
+  return sLon + String(" ") + sLat;
+}
+
+
+std::string SDMemTable::getFluxUnit() const
+{
+  String tmp;
+  table_.keywordSet().get("FluxUnit", tmp);
+  return tmp;
+}
+
+void SDMemTable::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 SDMemTable::setInstrument(const std::string& name)
+{
+  Bool throwIt = True;
+  Instrument ins = SDAttr::convertInstrument(name, throwIt);
+  String nameU(name);
+  nameU.upcase();
+  table_.rwKeywordSet().define(String("AntennaName"), nameU);
+}
+
+std::string SDMemTable::summary(bool verbose) const  {
+
+  // Format header info
+  ostringstream oss;
+  oss << endl;
+  oss << "--------------------------------------------------------------------------------" << endl;
+  oss << " Scan Table Summary" << endl;
+  oss << "--------------------------------------------------------------------------------" << 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() << endl
+      << setw(15) << "Channels:"  << setw(4) << nChan() << endl;
+  oss << endl;
+  String tmp;
+  table_.keywordSet().get("Observer", tmp);
+  oss << setw(15) << "Observer:" << tmp << 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;
+  Table t = table_.keywordSet().asTable("FREQUENCIES");
+  Vector<Double> vec;
+  t.keywordSet().get("RESTFREQS",vec);
+  oss << setw(15) << "Rest Freqs:";
+  if (vec.nelements() > 0) {
+      oss << setprecision(10) << vec << " [Hz]" << endl;
+  } else {
+      oss << "None set" << endl;
+  }
+  oss << setw(15) << "Abcissa:" << getAbcissaString() << endl;
+  oss << setw(15) << "Cursor:" << "Beam[" << getBeam() << "] "
+      << "IF[" << getIF() << "] " << "Pol[" << getPol() << "]" << endl;
+  oss << endl;
+
+  String dirtype ="Position ("+ MDirection::showType(getDirectionReference()) + ")";
+  oss << setw(5) << "Scan"
+      << setw(15) << "Source"
+      << setw(24) << dirtype
+      << setw(10) << "Time"
+      << setw(18) << "Integration"
+      << setw(7) << "FreqIDs" << endl;
+  oss << "--------------------------------------------------------------------------------" << endl;
+
+  // Generate list of scan start and end integrations
+  Vector<Int> scanIDs = scanCol_.getColumn();
+  Vector<uInt> startInt, endInt;
+  mathutil::scanBoundaries(startInt, endInt, scanIDs);
+
+  const uInt nScans = startInt.nelements();
+  String name;
+  Vector<uInt> freqIDs, listFQ;
+  Vector<uInt> restFreqIDs, listRestFQ;
+  uInt nInt;
+
+  for (uInt i=0; i<nScans; i++) {
+    // Get things from first integration of scan
+    String time = getTime(startInt(i),False);
+    String tInt = formatSec(Double(getInterval(startInt(i))));
+    String posit = formatDirection(getDirection(startInt(i),True));
+    srcnCol_.getScalar(startInt(i),name);
+
+    // Find all the FreqIDs in this scan
+    listFQ.resize(0);
+    listRestFQ.resize(0);
+    for (uInt j=startInt(i); j<endInt(i)+1; j++) {
+      freqidCol_.get(j, freqIDs);
+      for (uInt k=0; k<freqIDs.nelements(); k++) {
+        mathutil::addEntry(listFQ, freqIDs(k));
+      }
+//
+      restfreqidCol_.get(j, restFreqIDs);
+      for (uInt k=0; k<restFreqIDs.nelements(); k++) {
+        mathutil::addEntry(listRestFQ, restFreqIDs(k));
+      }
+    }
+
+    nInt = endInt(i) - startInt(i) + 1;
+    oss << setw(3) << std::right << i << std::left << setw(2) << "  "
+        << setw(15) << name
+        << setw(24) << posit
+        << setw(10) << time
+        << setw(3) << std::right << nInt  << setw(3) << " x " << std::left
+        << setw(6) <<  tInt
+        << " " << listFQ << " " << listRestFQ << endl;
+  }
+  oss << endl;
+  oss << "Table contains " << table_.nrow() << " integration(s) in "
+      << nScans << " scan(s)." << endl;
+
+  // Frequency Table
+  if (verbose) {
+    std::vector<string> info = getCoordInfo();
+    SDFrequencyTable sdft = getSDFreqTable();
+    oss << endl << endl;
+    oss << "FreqID  Frame   RefFreq(Hz)     RefPix   Increment(Hz)" << endl;
+    oss << "--------------------------------------------------------------------------------" << endl;
+    for (uInt i=0; i<sdft.length(); i++) {
+      oss << setw(8) << i << setw(8)
+          << info[3] << setw(16) << setprecision(8)
+          << sdft.referenceValue(i) << setw(10)
+          << sdft.referencePixel(i) << setw(12)
+          << sdft.increment(i) << endl;
+    }
+    oss << "--------------------------------------------------------------------------------" << endl;
+  }
+  return String(oss);
+}
+/*
+std::string SDMemTable::scanSummary(const std::vector<int>& whichScans) {
+  ostringstream oss;
+  Vector<Int> scanIDs = scanCol_.getColumn();
+  Vector<uInt> startInt, endInt;
+  mathutil::scanBoundaries(startInt, endInt, scanIDs);
+  const uInt nScans = startInt.nelements();
+  std::vector<int>::const_iterator it(whichScans);
+  return String(oss);
+}
+*/
+Int SDMemTable::nBeam() const
+{
+  Int n;
+  table_.keywordSet().get("nBeam",n);
+  return n;
+}
+
+Int SDMemTable::nIF() const {
+  Int n;
+  table_.keywordSet().get("nIF",n);
+  return n;
+}
+
+Int SDMemTable::nPol() const {
+  Int n;
+  table_.keywordSet().get("nPol",n);
+  return n;
+}
+
+Int SDMemTable::nChan() const {
+  Int n;
+  table_.keywordSet().get("nChan",n);
+  return n;
+}
+
+Table SDMemTable::getHistoryTable() const
+{
+  return table_.keywordSet().asTable("HISTORY");
+}
+
+void SDMemTable::appendToHistoryTable(const Table& otherHist)
+{
+  Table t = table_.rwKeywordSet().asTable("HISTORY");
+  const String sep = "--------------------------------------------------------------------------------";
+  addHistory(sep);
+  TableCopy::copyRows(t, otherHist, t.nrow(), 0, otherHist.nrow());
+  addHistory(sep);
+}
+
+void SDMemTable::addHistory(const std::string& hist)
+{
+  Table t = table_.rwKeywordSet().asTable("HISTORY");
+  uInt nrow = t.nrow();
+  t.addRow();
+  ScalarColumn<String> itemCol(t, "ITEM");
+  itemCol.put(nrow, hist);
+}
+
+std::vector<std::string> SDMemTable::getHistory() const
+{
+  Vector<String> history;
+  const Table& t = table_.keywordSet().asTable("HISTORY");
+  uInt nrow = t.nrow();
+  ROScalarColumn<String> itemCol(t, "ITEM");
+  std::vector<std::string> stlout;
+  String hist;
+  for (uInt i=0; i<nrow; ++i) {
+    itemCol.get(i, hist);
+    stlout.push_back(hist);
+  }
+  return stlout;
+}
+
+
+/*
+  void SDMemTable::maskChannels(const std::vector<Int>& whichChans ) {
+
+  std::vector<int>::iterator it;
+  ArrayAccessor<uChar, Axis<asap::PolAxis> > j(flags_);
+  for (it = whichChans.begin(); it != whichChans.end(); it++) {
+    j.reset(j.begin(uInt(*it)));
+    for (ArrayAccessor<uChar, Axis<asap::BeamAxis> > i(j); i != i.end(); ++i) {
+      for (ArrayAccessor<uChar, Axis<asap::IFAxis> > ii(i); ii != ii.end(); ++ii) {
+        for (ArrayAccessor<uChar, Axis<asap::ChanAxis> > iii(ii);
+             iii != iii.end(); ++iii) {
+          (*iii) =
+        }
+      }
+    }
+  }
+
+}
+*/
+void SDMemTable::flag(int whichRow)
+  {
+  Array<uChar> arr;
+  flagsCol_.get(whichRow, arr);
+
+  ArrayAccessor<uChar, Axis<asap::BeamAxis> > aa0(arr);
+  aa0.reset(aa0.begin(uInt(beamSel_)));//go to beam
+  ArrayAccessor<uChar, Axis<asap::IFAxis> > aa1(aa0);
+  aa1.reset(aa1.begin(uInt(IFSel_)));// go to IF
+  ArrayAccessor<uChar, Axis<asap::PolAxis> > aa2(aa1);
+  aa2.reset(aa2.begin(uInt(polSel_)));// go to pol
+
+  for (ArrayAccessor<uChar, Axis<asap::ChanAxis> > i(aa2); i != i.end(); ++i) {
+    (*i) = uChar(True);
+  }
+
+  flagsCol_.put(whichRow, arr);
+}
+
+MDirection::Types SDMemTable::getDirectionReference() const
+{
+  Float eq;
+  table_.keywordSet().get("Equinox",eq);
+  std::map<float,string> mp;
+  mp[2000.0] = "J2000";
+  mp[1950.0] = "B1950";
+  MDirection::Types mdr;
+  if (!MDirection::getType(mdr, mp[eq])) {
+    mdr = MDirection::J2000;
+    pushLog("WARNING: Unknown equinox using J2000");
+  }
+
+  return mdr;
+}
+
+MEpoch::Types SDMemTable::getTimeReference() const
+{
+  MEpoch::Types met;
+  String ep;
+  table_.keywordSet().get("Epoch",ep);
+  if (!MEpoch::getType(met, ep)) {
+     pushLog("WARNING: Epoch type unknown - using UTC");
+    met = MEpoch::UTC;
+  }
+
+  return met;
+}
+
+
+Bool SDMemTable::setRestFreqs(const Vector<Double>& restFreqsIn,
+                              const String& sUnit,
+                              const vector<string>& lines,
+                              const String& source,
+                              Int whichIF)
+{
+   const Int nIFs = nIF();
+   if (whichIF>=nIFs) {
+      throw(AipsError("Illegal IF index"));
+   }
+
+   // Find vector of restfrequencies
+   // Double takes precedence over String
+   Unit unit;
+   Vector<Double> restFreqs;
+   if (restFreqsIn.nelements()>0) {
+      restFreqs.resize(restFreqsIn.nelements());
+      restFreqs = restFreqsIn;
+      unit = Unit(sUnit);
+   } else if (lines.size()>0) {
+      const uInt nLines = lines.size();
+      unit = Unit("Hz");
+      restFreqs.resize(nLines);
+      MFrequency lineFreq;
+      for (uInt i=0; i<nLines; i++) {
+         String tS(lines[i]);
+         tS.upcase();
+         if (MeasTable::Line(lineFreq, tS)) {
+            restFreqs[i] = lineFreq.getValue().getValue();          // Hz
+         } else {
+            String s = String(lines[i]) +
+              String(" is an unrecognized spectral line");
+            throw(AipsError(s));
+         }
+      }
+   } else {
+      throw(AipsError("You have not specified any rest frequencies or lines"));
+   }
+
+   // If multiple restfreqs, must be length nIF. In this
+   // case we will just replace the rest frequencies
+   // We can't disinguish scalar and vector of length 1
+   const uInt nRestFreqs = restFreqs.nelements();
+   Int idx = -1;
+   SDFrequencyTable sdft = getSDFreqTable();
+
+   ostringstream oss;
+   if (nRestFreqs>1) {
+     // Replace restFreqs, one per IF
+     if (nRestFreqs != nIFs) {
+       throw (AipsError("Number of rest frequencies must be equal to the number of IFs"));
+     }
+     ostringstream oss;
+     oss << "Replaced rest frequencies, one per IF, with given list : " << restFreqs;
+     sdft.deleteRestFrequencies();
+     for (uInt i=0; i<nRestFreqs; i++) {
+       Quantum<Double> rf(restFreqs[i], unit);
+       sdft.addRestFrequency(rf.getValue("Hz"));
+     }
+   } else {
+
+     // Add new rest freq
+      Quantum<Double> rf(restFreqs[0], unit);
+      idx = sdft.addRestFrequency(rf.getValue("Hz"));
+      if (whichIF>=0) {
+         oss << "Selected given rest frequency (" << restFreqs[0] << ") for IF " << whichIF << endl;
+      } else {
+         oss << "Selected given rest frequency (" << restFreqs[0] << ") for all IFs" << endl;
+      }
+   }
+   pushLog(String(oss));
+   // Replace
+   Bool empty = source.empty();
+   Bool ok = False;
+   if (putSDFreqTable(sdft)) {
+      const uInt nRow = table_.nrow();
+      String srcName;
+      Vector<uInt> restFreqIDs;
+      for (uInt i=0; i<nRow; i++) {
+         srcnCol_.get(i, srcName);
+         restfreqidCol_.get(i,restFreqIDs);
+         if (idx==-1) {
+           // Replace vector of restFreqs; one per IF.
+           // No selection possible
+            for (uInt i=0; i<nIFs; i++) restFreqIDs[i] = i;
+         } else {
+           // Set RestFreqID for selected data
+            if (empty || source==srcName) {
+               if (whichIF<0) {
+                  restFreqIDs = idx;
+               } else {
+                  restFreqIDs[whichIF] = idx;
+               }
+            }
+         }
+         restfreqidCol_.put(i,restFreqIDs);
+      }
+      ok = True;
+   } else {
+     ok = False;
+   }
+
+   return ok;
+}
+
+std::string SDMemTable::spectralLines() const
+{
+   Vector<String> lines = MeasTable::Lines();
+   MFrequency lineFreq;
+   Double freq;
+   ostringstream oss;
+
+   oss.flags(std::ios_base::left);
+   oss << "Line      Frequency (Hz)" << endl;
+   oss << "-----------------------" << endl;
+   for (uInt i=0; i<lines.nelements(); i++) {
+     MeasTable::Line(lineFreq, lines[i]);
+     freq = lineFreq.getValue().getValue();          // Hz
+     oss << setw(11) << lines[i] << setprecision(10) << freq << endl;
+   }
+   return String(oss);
+}
+
+void SDMemTable::renumber()
+{
+  uInt nRow = scanCol_.nrow();
+  Int newscanid = 0;
+  Int cIdx;// the current scanid
+  // get the first scanid
+  scanCol_.getScalar(0,cIdx);
+  Int pIdx = cIdx;// the scanid of the previous row
+  for (uInt i=0; i<nRow;++i) {
+    scanCol_.getScalar(i,cIdx);
+    if (pIdx == cIdx) {
+      // renumber
+      scanCol_.put(i,newscanid);
+    } else {
+      ++newscanid;
+      pIdx = cIdx;   // store scanid
+      --i;           // don't increment next loop
+    }
+  }
+}
+
+
+void SDMemTable::getCursorSlice(IPosition& start, IPosition& end,
+                                const IPosition& shape) const
+{
+  const uInt nDim = shape.nelements();
+  start.resize(nDim);
+  end.resize(nDim);
+
+  start(asap::BeamAxis) = beamSel_;
+  end(asap::BeamAxis) = beamSel_;
+  start(asap::IFAxis) = IFSel_;
+  end(asap::IFAxis) = IFSel_;
+
+  start(asap::PolAxis) = polSel_;
+  end(asap::PolAxis) = polSel_;
+
+  start(asap::ChanAxis) = 0;
+  end(asap::ChanAxis) = shape(asap::ChanAxis) - 1;
+}
+
+
+std::vector<float> SDMemTable::getFloatSpectrum(const Array<Float>& arr) const
+  // Get spectrum at cursor location
+{
+
+  // Setup accessors
+  ArrayAccessor<Float, Axis<asap::BeamAxis> > aa0(arr);
+  aa0.reset(aa0.begin(uInt(beamSel_)));                    // Beam selection
+
+  ArrayAccessor<Float, Axis<asap::IFAxis> > aa1(aa0);
+  aa1.reset(aa1.begin(uInt(IFSel_)));                      // IF selection
+
+  ArrayAccessor<Float, Axis<asap::PolAxis> > aa2(aa1);
+  aa2.reset(aa2.begin(uInt(polSel_)));                     // Pol selection
+
+  std::vector<float> spectrum;
+  for (ArrayAccessor<Float, Axis<asap::ChanAxis> > i(aa2); i != i.end(); ++i) {
+    spectrum.push_back(*i);
+  }
+  return spectrum;
+}
+
+void SDMemTable::calculateAZEL()
+{
+  MPosition mp = getAntennaPosition();
+  ostringstream oss;
+  oss << "Computed azimuth/elevation using " << endl
+      << mp << endl;
+  for (uInt i=0; i<nRow();++i) {
+    MEpoch me = getEpoch(i);
+    MDirection md = getDirection(i,False);
+    oss  << " Time: " << getTime(i,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,azel[0]);
+    elCol_.put(i,azel[1]);
+    oss << "azel: " << azel[0]/C::pi*180.0 << " "
+        << azel[1]/C::pi*180.0 << " (deg)" << endl;
+  }
+  pushLog(String(oss));
+}
Index: tags/asap1/src/SDMemTable.h
===================================================================
--- tags/asap1/src/SDMemTable.h	(revision 819)
+++ tags/asap1/src/SDMemTable.h	(revision 819)
@@ -0,0 +1,310 @@
+//#---------------------------------------------------------------------------
+//# SDMemTable.h: A MemoryTable container 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 SDMEMTABLE_H
+#define SDMEMTABLE_H
+
+// STL
+#include <string>
+#include <vector>
+// AIPS++
+#include <casa/aips.h>
+#include <casa/Arrays/MaskedArray.h>
+#include <casa/BasicSL/String.h>
+#include <coordinates/Coordinates/SpectralCoordinate.h>
+#include <tables/Tables/Table.h>
+#include <tables/Tables/ArrayColumn.h>
+#include <tables/Tables/ScalarColumn.h>
+
+#include "SDDefs.h"
+#include "SDFitTable.h"
+#include "SDLog.h"
+
+namespace asap {
+
+class SDContainer;
+class SDHeader;
+class SDFrequencyTable;
+class SDFitTable;
+
+
+class SDMemTable : private SDLog {
+public:
+  // create a new (empty) SDMemTable
+  SDMemTable();
+  // create a SDMemTable from an (aips++) table on disk
+  SDMemTable(const std::string& name);
+
+  // Copy Construct a SDMemTable, if clear==True only header and
+  // skeleton are copied, otherwise the whole table is copied.
+  SDMemTable(const SDMemTable& other, casa::Bool clear=casa::False);
+
+  // Copy Construct (copy semantics) a SDMemTable, give a scanid constraint
+  // see also getScan()
+  SDMemTable(const casa::Table& tab, const std::string& expr);
+
+  // Assignment operator (copy semantics)
+  SDMemTable &operator=(const SDMemTable& other);
+
+  virtual ~SDMemTable();
+
+  // put data from meta conatiner into the table
+  bool putSDContainer(const SDContainer& sdc);
+  bool putSDHeader(const SDHeader& sdh);
+  bool putSDFreqTable(const SDFrequencyTable& sdft);
+
+  //get the data wrapped up in a meta container
+
+  SDContainer getSDContainer(casa::uInt whichRow=0) const;
+  SDHeader getSDHeader() const;
+
+  // Get SD Frequency table.
+  SDFrequencyTable getSDFreqTable() const;
+
+  // get spectrum,mask and tsys for the given row, at the selected
+  // cursor - all as stl vectors
+  virtual std::vector<float> getSpectrum(casa::Int whichRow=0) const;
+  virtual std::vector<bool> getMask(casa::Int whichRow=0) const;
+
+  // Get length of STokes spectrum. XX & YY -> I
+  // Anything else the length stays the same.
+  int nStokes() const;
+
+  // Get specific Stokes at cursor location. One of either I,Q,U,V or
+  // I,P,PA,V (doPol=True) (determined by the polSel cursor location
+  // 0->3) If the latter, you can add a PA offset (degrees)
+  virtual std::vector<float> getStokesSpectrum(casa::Int whichRow=0,
+                                               casa::Bool doPol=casa::False) const;
+
+  // Returns Label for polarization selected by polSel_ (or
+  // over-ridden with arg. polIdx).  If doStokes is False, returns
+  // label for raw correlations (linear or circular). If True returns
+  // label for Stokes.  If doPol=False, I,Q,U,V else I,P,PA,V
+  std::string getPolarizationLabel(casa::Bool linear, casa::Bool stokes,
+                                   casa::Bool linPol,
+                                   casa::Int polIdx=-1) const;
+
+  // Convert Stokes to linear or circular polarizations (specified by
+  // polIdx=0:3).  Presently, only conversion to RR or LL is
+  // supported.
+  virtual std::vector<float> stokesToPolSpectrum(casa::Int whichRow,
+                                                 casa::Bool toLinear,
+                                                 casa::Int polIdx) const;
+
+  // Get all Stokes at the specified Beam/IF cursor location (ignoring
+  // the internal cursor).  -1 means all on that axis.  Really, this function
+  // should not be here, and the SDContainer should hold the stokes spectrum
+  // as well.  However, because the Stokes column is virtual, and its shape
+  // is not the same as nPol(), I have put it here for direct access as needed
+  virtual casa::Array<casa::Float> getStokesSpectrum(casa::Int whichRow=0,
+                                                     casa::Int iBeam=-1,
+                                                     casa::Int iIF=-1) const;
+
+  // Get Tsys at cursor location
+  virtual casa::Float getTsys(casa::Int whichRow=0) const;
+
+  // get all as aips++ Vectors
+  virtual void getSpectrum(casa::Vector<casa::Float>& spectrum,
+                           casa::Int whichRow=0) const;
+
+  // Get rest frequencies
+  std::vector<double> getRestFreqs() const;
+
+  // get info for current row
+  // if whichRow == -1 the Header time is given
+  std::string getTime(casa::Int whichRow=0,
+                      casa::Bool showDate=casa::False) const ;
+  casa::MEpoch getEpoch(casa::Int whichRow=0) const;
+  casa::MDirection getDirection(casa::Int whichRow=0,
+                                casa::Bool refBeam=casa::False) const;
+
+  std::string getSourceName(casa::Int whichRow=0) const;
+
+  float getElevation(casa::Int whichRow=0) const;
+  float getAzimuth(casa::Int whichRow=0) const;
+  float getParAngle(casa::Int whichRow=0) const;
+
+  double getInterval(casa::Int whichRow=0) const;
+
+  virtual void setSpectrum(std::vector<float> spectrum, int whichRow=0);
+  virtual void setCoordInfo(std::vector<string> theinfo);
+
+  // Set RestFreqID.  source="" and IF=-1 means select all
+  virtual casa::Bool setRestFreqs(const casa::Vector<casa::Double>& restFreqs,
+                                  const casa::String& unit,
+                                  const std::vector<std::string>& lines,
+                                  const casa::String& source,
+                                  casa::Int whichIF=-1);
+
+  // List lines
+  std::string spectralLines() const;
+
+  // Get/Set flux unit
+  std::string getFluxUnit() const;
+  void setFluxUnit (const std::string& unit);
+
+  // Set Instrument
+  void setInstrument (const std::string& instrument);
+
+  // set the current value
+  virtual bool setIF(casa::Int whichIF=0);
+  virtual bool setBeam(casa::Int whichBeam=0);
+  virtual bool setPol(casa::Int whichPol=0);
+
+  // REset cursor to 0
+  virtual void resetCursor();
+
+  // Hard flags the current spectrum, not reversible
+  virtual void flag(int whichRow);
+
+  // return the currently selected values
+  virtual casa::Int getIF() const { return IFSel_; }
+  virtual casa::Int getBeam() const { return beamSel_; }
+  virtual casa::Int getPol() const { return polSel_; }
+
+  // returns unit, conversion frame, doppler, base-frame
+  virtual std::vector<std::string> getCoordInfo() const;
+
+  // number of scans in table
+  virtual casa::Int nScan() const;
+
+  // get a summary of the table
+  virtual std::string summary(bool verbose=false) const;
+
+  // get/set the history
+  std::vector<std::string> getHistory() const;
+  void addHistory(const std::string& hist);
+
+  casa::Table getHistoryTable() const;
+  void appendToHistoryTable(const casa::Table& otherHist);
+
+  // write to disk as aips++ table
+  void makePersistent(const std::string& filename);
+
+  // get a new SDMemTable containing all rows with the same give SCANID
+  SDMemTable getScan(casa::Int scanID) const;
+  SDMemTable getSource(const std::string& source) const;
+
+  const casa::TableRecord& getHeader() const {return table_.keywordSet();}
+  // get a handle to the "raw" aips++ table
+  const casa::Table& table() const { return table_; }
+
+  // return the number of values
+  casa::Int nBeam() const;
+  casa::Int nIF() const;
+  casa::Int nPol() const;
+  casa::Int nChan() const;
+
+  // return the number of rows (integrations) in the table
+  casa::Int nRow() const { return table_.nrow(); }
+
+  // return a row as a Masked array, internally converting uChar flags
+  // to bool mask
+  casa::MaskedArray<casa::Float> rowAsMaskedArray(casa::uInt whichRow,
+                                                  casa::Bool toStokes=casa::False) const;
+
+  // Return SC, setting only the basic construction state (i.e.
+  // no conversion or velocity or rest frequency state).
+  // Specify the index of the FreqID you want
+  casa::SpectralCoordinate getSpectralCoordinate(casa::uInt whichIdx) const;
+
+  // Return SC. Set velocity conversion state (unit,doppler), and
+  // rest frequency.  If row number given (>=0), also set
+  // frame conversion layer (needs direction & time which require row)
+  casa::SpectralCoordinate getSpectralCoordinate(casa::uInt freqID,
+                                                 casa::uInt restFreqID,
+                                                 casa::uInt row) const;
+
+  // Set just the reference value, pixel and increment into the table
+  // No other state is extracted.
+  casa::Bool setCoordinate(const casa::SpectralCoordinate& speccord,
+                           casa::uInt whichIdx);
+
+  casa::Int nCoordinates() const;
+
+  std::vector<double> getAbcissa(int whichRow=0) const;
+  std::string getAbcissaString(casa::Int whichRow=0) const;
+
+// Get global reference  types
+  casa::MDirection::Types getDirectionReference() const;
+  casa::MEpoch::Types getTimeReference() const;
+
+// Get global antenna position
+  casa::MPosition getAntennaPosition() const;
+
+  bool putSDFitTable(const SDFitTable& sdft);
+  SDFitTable getSDFitTable() const;
+  SDFitTable getSDFitTable(casa::uInt whichRow) const;
+
+  void addFit(casa::uInt whichRow,
+              const casa::Vector<casa::Double>& p,
+              const casa::Vector<casa::Bool>& m,
+              const casa::Vector<casa::String>& f,
+              const casa::Vector<casa::Int>& c);
+
+
+  void calculateAZEL();
+
+private:
+  // utility func for nice printout
+  casa::String formatSec(casa::Double x) const;
+  casa::String formatDirection(const casa::MDirection& md) const;
+  std::vector<float> getFloatSpectrum(const casa::Array<casa::Float>& arr) const;
+  void setup();
+  void attach();
+  void renumber();
+
+  // Generate start and end for shape and current cursor selection
+  void getCursorSlice(casa::IPosition& start, casa::IPosition& end,
+                      const casa::IPosition& shape) const;
+
+  // the current cursor into the array
+  casa::Int IFSel_,beamSel_,polSel_;
+  // the underlying memory table
+  casa::Table table_;
+  // The current table version
+  static const casa::uInt version_ = 1;
+
+  // Cached Columns to avoid reconstructing them for each row get/put
+  casa::ScalarColumn<casa::Double> timeCol_, integrCol_;
+  casa::ScalarColumn<casa::Float> azCol_, elCol_, paraCol_;
+  casa::ScalarColumn<casa::String> srcnCol_, fldnCol_, tcaltCol_;
+  casa::ScalarColumn<casa::Int> scanCol_, rbeamCol_;
+  casa::ArrayColumn<casa::Float> specCol_, tsCol_, tcalCol_;
+  casa::ArrayColumn<casa::Double> dirCol_;
+  casa::ArrayColumn<casa::uChar> flagsCol_;
+  casa::ArrayColumn<casa::uInt> freqidCol_, restfreqidCol_;
+  casa::ArrayColumn<casa::String> histCol_;
+  casa::ArrayColumn<casa::Int> fitCol_;
+  casa::ROArrayColumn<casa::Float> stokesCol_;
+};
+
+}// namespace
+#endif
Index: tags/asap1/src/SDMemTableWrapper.h
===================================================================
--- tags/asap1/src/SDMemTableWrapper.h	(revision 819)
+++ tags/asap1/src/SDMemTableWrapper.h	(revision 819)
@@ -0,0 +1,237 @@
+//#---------------------------------------------------------------------------
+//# SDMemTableWrapper.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 SDMEMTABLEWRAPPER_H
+#define SDMEMTABLEWRAPPER_H
+
+#include <vector>
+#include <string>
+#include <casa/Arrays/Vector.h>
+
+#include "MathUtils.h"
+#include "SDFitTable.h"
+#include "SDMemTable.h"
+
+namespace asap {
+
+class SDMemTableWrapper {
+
+public:
+  SDMemTableWrapper(const std::string& name) :
+    table_(new SDMemTable(name)) {;}
+  SDMemTableWrapper() :
+    table_(new SDMemTable()) {;}
+
+  SDMemTableWrapper(casa::CountedPtr<SDMemTable> cp) : table_(cp) {;}
+  //SDMemTableWrapper(SDMemTable* sdmt) : table_(sdmt) {;}
+  SDMemTableWrapper(const SDMemTableWrapper& mt) :
+    table_(mt.getCP()) {;}
+
+  SDMemTableWrapper(const SDMemTableWrapper& mt, const std::string& expr) :
+    table_(new SDMemTable(mt.getCP()->table(), expr)) {;}
+
+  SDMemTableWrapper copy() {
+    //CountedPtr<SDMemTable> cp = new SDMemTable(*this, False);
+    return SDMemTableWrapper(new SDMemTable(*(this->getCP()), casa::False));
+  }
+
+  SDMemTableWrapper getScan(std::vector<int> scan) {
+    casa::String cond("SELECT FROM $1 WHERE SCANID IN ");
+    casa::Vector<casa::Int> v(scan);
+    casa::ostringstream oss;
+    oss << v;
+    cond += casa::String(oss);
+    return SDMemTableWrapper(*this, cond);
+  }
+
+  SDMemTableWrapper getSource(const std::string& source) {
+    casa::String cond("SELECT * from $1 WHERE SRCNAME == pattern('");
+    cond += source;cond += "')";
+    return SDMemTableWrapper(*this, cond);
+  }
+
+  SDMemTableWrapper getSQL(const std::string& sqlexpr) {
+    return SDMemTableWrapper(*this, sqlexpr);
+  }
+
+  std::vector<float> getSpectrum(int whichRow=0) const {
+    return table_->getSpectrum(whichRow);
+  }
+
+  std::vector<float> getStokesSpectrum(int whichRow=0,
+                                       bool linPol=false) const {
+    return table_->getStokesSpectrum(whichRow, linPol);
+  }
+
+  std::vector<float> stokesToPolSpectrum(int whichRow=0, bool linear=false,
+                                         int polIdx=-1) const {
+    return table_->stokesToPolSpectrum(whichRow, linear, polIdx);
+  }
+
+  //  std::string getPolarizationLabel(bool linear, bool stokes, bool linPol, int polIdx) const {
+  // Boost fails with 4 arguments.
+  std::string getPolarizationLabel(bool linear, bool stokes,
+                                   bool linPol) const {
+    int polIdx = -1;
+    return table_->getPolarizationLabel(linear, stokes, linPol, polIdx);
+  }
+
+  std::vector<double> getAbcissa(int whichRow=0) const {
+    return table_->getAbcissa(whichRow);
+  }
+  std::string getAbcissaString(int whichRow=0) const {
+    return table_->getAbcissaString(whichRow);
+  }
+
+  std::vector<float> getTsys() {
+     int nRow = table_->nRow();
+     std::vector<float> result(nRow);
+     for (uint i=0; i<nRow; i++) {
+        result[i] = table_->getTsys(i);
+     }
+     return result;
+  }
+
+  std::string getTime(int whichRow=0) {return table_->getTime(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);}
+
+  std::vector<bool> getMask(int whichRow=0) const {
+    return table_->getMask(whichRow);
+  }
+
+  void flag(int whichRow=-1) {
+    table_->flag(whichRow);
+  }
+  std::string getSourceName(int whichRow=0) {
+    return table_->getSourceName(whichRow);
+  }
+
+  float getElevation(int whichRow=0) {
+    return table_->getElevation(whichRow);
+  }
+  float getAzimuth(int whichRow=0) {
+    return table_->getAzimuth(whichRow);
+  }
+  float getParAngle(int whichRow=0) {
+    return table_->getParAngle(whichRow);
+  }
+
+  void setSpectrum(std::vector<float> spectrum, int whichRow=0) {
+      table_->setSpectrum(spectrum, whichRow);
+  }
+
+  bool setIF(int whichIF=0) {return table_->setIF(whichIF);}
+  bool setBeam(int whichBeam=0) {return table_->setBeam(whichBeam);}
+  bool setPol(int whichPol=0) {return table_->setPol(whichPol);}
+
+  int getIF() {return table_->getIF();}
+  int getBeam() {return table_->getBeam();}
+  int getPol() {return table_->getPol();}
+
+  int nIF() {return table_->nIF();}
+  int nBeam() {return table_->nBeam();}
+  int nPol() {return table_->nPol();}
+  int nChan() {return table_->nChan();}
+  int nScan() {return table_->nScan();}
+  int nRow() {return table_->nRow();}
+  int nStokes() {return table_->nStokes();}
+
+  //sets the mask
+  bool setChannels(const std::vector<int>& whichChans) {
+    return setChannels(whichChans);
+  }
+  void makePersistent(const std::string& fname) {
+    table_->makePersistent(fname);
+  }
+
+  bool setRestFreqs(const std::vector<double>& restfreqs,
+                    const std::string& unit,
+                    const std::vector<std::string>& lines,
+                    const std::string& source, int whichIF) {
+    casa::Vector<casa::Double> restFreqs2(restfreqs);
+    return table_->setRestFreqs(restFreqs2, casa::String(unit),
+                                lines, casa::String(source),
+                                casa::Int(whichIF));
+  }
+
+  std::string spectralLines() const {return table_->spectralLines();}
+
+  std::vector<double> getRestFreqs() {
+    return table_->getRestFreqs();
+  }
+
+  void setCoordInfo(std::vector<string> theinfo) {
+    table_->setCoordInfo(theinfo);
+  }
+  std::vector<string> getCoordInfo() const {
+    return table_->getCoordInfo();
+  }
+
+  casa::CountedPtr<SDMemTable> getCP() const {return table_;}
+  SDMemTable* getPtr() {return &(*table_);}
+
+  std::string summary(bool verbose=false) const {
+    return table_->summary(verbose);
+  }
+
+  std::vector<std::string> getHistory() {
+    return table_->getHistory();
+  }
+  void addHistory(const std::string& hist) {
+    table_->addHistory(hist);
+  }
+
+  void addFit(int whichRow, const std::vector<double>& p,
+              const std::vector<bool>& m, const std::vector<string>& f,
+              const std::vector<int>& c) {
+
+    casa::Vector<casa::Double> p2(p);
+    casa::Vector<casa::Bool> m2(m);
+    casa::Vector<casa::String> f2 = mathutil::toVectorString(f);
+    casa::Vector<casa::Int> c2(c);
+    table_->addFit(casa::uInt(whichRow), p2,m2,f2,c2);
+  }
+  SDFitTable getSDFitTable(int whichRow) {
+    return table_->getSDFitTable(casa::uInt(whichRow));
+  }
+
+  void calculateAZEL() { table_->calculateAZEL(); };
+
+private:
+  casa::CountedPtr<SDMemTable> table_;
+};
+
+} // namespace
+#endif
+
Index: tags/asap1/src/SDPol.cc
===================================================================
--- tags/asap1/src/SDPol.cc	(revision 819)
+++ tags/asap1/src/SDPol.cc	(revision 819)
@@ -0,0 +1,563 @@
+//#---------------------------------------------------------------------------
+//# SDPol.cc: Polarimetric functionality
+//#---------------------------------------------------------------------------
+//# 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 "SDPol.h"
+#include "SDDefs.h"
+
+#include <casa/Arrays/Array.h>
+#include <casa/Arrays/ArrayMath.h>
+#include <casa/Arrays/ArrayLogical.h>
+#include <casa/Arrays/VectorIter.h>
+#include <casa/Containers/Record.h>
+#include <casa/BasicSL/Constants.h>
+#include <casa/BasicSL/String.h>
+#include <casa/Utilities/Assert.h>
+
+#include <tables/Tables/Table.h>
+#include <tables/Tables/ScalarColumn.h>
+#include <tables/Tables/ArrayColumn.h>
+#include <tables/Tables/ColumnDesc.h>
+#include <tables/Tables/TableRecord.h>
+#include <tables/Tables/DataManError.h>
+
+
+using namespace casa;
+using namespace asap;
+
+
+
+SDStokesEngine::SDStokesEngine (const String& outputColumnName,
+			   const String& inputColumnName)
+: BaseMappedArrayEngine<Float,Float> (outputColumnName, inputColumnName)
+{
+   setWritable(False);
+}
+
+
+SDStokesEngine::SDStokesEngine (const Record& spec)
+: BaseMappedArrayEngine<Float,Float> ()
+{
+    setWritable(False);
+    if (spec.isDefined("OUTPUTNAME")  &&  spec.isDefined("INPUTNAME")) {
+        setNames (spec.asString("OUTPUTNAME"), spec.asString("INPUTNAME"));
+    }
+}
+
+SDStokesEngine::SDStokesEngine (const SDStokesEngine& that)
+: BaseMappedArrayEngine<Float,Float> (that)
+{}
+
+SDStokesEngine::~SDStokesEngine()
+{}
+
+
+DataManager* SDStokesEngine::clone() const
+{
+    DataManager* dmPtr = new SDStokesEngine (*this);
+    return dmPtr;
+}
+
+
+String SDStokesEngine::dataManagerType() const
+{
+    return className();
+}
+
+String SDStokesEngine::className()
+{
+    return "SDStokesEngine";
+}
+
+String SDStokesEngine::dataManagerName() const
+{
+    return virtualName();
+}
+
+Record SDStokesEngine::dataManagerSpec() const
+{
+    Record spec;
+    spec.define ("OUTPUTNAME", virtualName());
+    spec.define ("INPUTNAME", storedName());
+    return spec;
+}
+
+DataManager* SDStokesEngine::makeObject (const String&, const Record& spec)
+{
+    DataManager* dmPtr = new SDStokesEngine(spec);
+    return dmPtr;
+}
+
+
+void SDStokesEngine::registerClass()
+{
+    DataManager::registerCtor (className(), makeObject);
+}
+
+
+void SDStokesEngine::create (uInt initialNrrow)
+{
+    BaseMappedArrayEngine<Float,Float>::create (initialNrrow);
+}
+
+void SDStokesEngine::prepare()
+{
+    BaseMappedArrayEngine<Float,Float>::prepare();
+}
+
+Bool SDStokesEngine::canAccessArrayColumnCells (Bool& reask) const
+{
+    reask = False;
+    return True;
+}
+
+
+void SDStokesEngine::getArray (uInt rownr, Array<Float>& output)
+{
+    Array<Float> input;
+    roColumn().get(rownr, input);
+//
+    computeOnGet (output, input);
+}
+
+void SDStokesEngine::putArray (uInt rownr, const Array<Float>& input)
+{
+    throw(AipsError("This Virtual Column is not writable"));
+}
+
+    
+IPosition SDStokesEngine::shape (uInt rownr)
+{
+   IPosition inputShape = roColumn().shape (rownr);
+   return findOutputShape(inputShape);
+}
+
+
+
+void SDStokesEngine::computeOnGet(Array<Float>& output,
+                   		 const Array<Float>& input)
+//
+// array of shape (nBeam,nIF,nPol,nChan)
+//
+// We use the scaling convention I=(XX+YY)/2
+
+{
+
+// Checks
+
+   const uInt nDim = input.ndim();
+   AlwaysAssert(nDim==4,AipsError);
+   AlwaysAssert(output.ndim()==4,AipsError);
+//
+   const IPosition inputShape = input.shape();
+   const uInt polAxis = asap::PolAxis;
+   const uInt nPol = inputShape(polAxis);
+   AlwaysAssert(nPol==1 || nPol==2 || nPol==4, AipsError);
+
+// The silly Array slice operator does not give me back
+// a const reference so have to caste it away
+
+   Array<Float>& input2 = const_cast<Array<Float>&>(input);
+
+// Slice coordnates
+
+   IPosition start(nDim,0);
+   IPosition end(input.shape()-1);
+
+// Generate Slices
+
+   start(polAxis) = 0;
+   end(polAxis) = 0;
+   Array<Float> C1 = input2(start,end);          // Input : C1
+//
+   start(polAxis) = 0;
+   end(polAxis) = 0;
+   Array<Float> I = output(start,end);           // Output : I
+//
+   if (nPol==1) {
+      I = C1;
+      return;
+   }
+//
+   start(polAxis) = 1;
+   end(polAxis) = 1;
+   Array<Float> C2 = input2(start,end);          // Input : C1
+//
+   I = Float(0.5) * (C1 + C2);
+   if (nPol <= 2) return;
+//
+   start(polAxis) = 2;
+   end(polAxis) = 2;
+   Array<Float> C3 = input2(start,end);          // Input : C3
+//
+   start(polAxis) = 3;
+   end(polAxis) = 3;
+   Array<Float> C4 = input2(start,end);          // Input : C4
+//
+   start(polAxis) = 1;
+   end(polAxis) = 1;
+   Array<Float> Q = output(start,end);           // Output : Q
+   Q = Float(0.5) * (C1 - C2);
+//
+   start(polAxis) = 2;
+   end(polAxis) = 2;
+   Array<Float> U = output(start,end);           // Output : U
+   U = C3;
+//
+   start(polAxis) = 3;
+   end(polAxis) = 3;
+   Array<Float> V = output(start,end);           // Output : V
+   V = C4;
+}
+
+
+
+IPosition SDStokesEngine::findOutputShape (const IPosition& inputShape) const
+{
+   uInt axis = 2;
+   uInt nPol = inputShape(axis);
+   IPosition outputShape = inputShape;
+   if (nPol==1) {
+      outputShape(axis) = 1;            // XX -> I
+   } else if (nPol==2) {
+      outputShape(axis) = 1;            // XX YY -> I
+   } else if (nPol==4) {
+      outputShape(axis) = 4;            // XX YY R(XY) I(XY) -> I Q U V
+   }
+   return outputShape;
+}
+
+
+
+// SDPolUtil
+
+Array<Float> SDPolUtil::polarizedIntensity (const Array<Float>& Q,
+                                            const Array<Float>& U)
+{
+   Array<Float> t1 = pow(Q,Double(2.0));
+   Array<Float> t2 = pow(U,Double(2.0));
+   return sqrt(t1+t2);
+}
+
+
+Array<Float> SDPolUtil::positionAngle (const Array<Float>& Q,
+                                       const Array<Float>& U)
+{
+   return Float(180.0/C::pi/2.0)*atan2(U,Q);       // Degrees
+}
+
+
+void SDPolUtil::rotatePhase (Array<Float>& R,
+                             Array<Float>& I,
+                             Float phase)
+//
+// Apply phase rotation to Z = (R + iI)
+//
+{
+   Float cosVal = cos(C::pi/180.0*phase);
+   Float sinVal = sin(C::pi/180.0*phase);
+//
+   Array<Float> R2 = R*cosVal - I*sinVal;
+   I =  R*sinVal + I*cosVal;
+   R = R2;
+}
+
+
+void SDPolUtil::rotateLinPolPhase (Array<Float>& C1,
+                                   Array<Float>& C2,
+                                   Array<Float>& C3,
+                                   Array<Float>& I,
+                                   Array<Float>& Q,
+                                   Array<Float>& U,
+                                   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)
+//
+{
+// Rotate Q & U (factor of 2 for polarization)
+
+   rotatePhase(Q, U, 2.0*phase);
+
+// Now recompute C1,C2,C3 
+// C4 unchanged 
+
+   C1 = I + Q;
+   C2 = I - Q;
+   C3 = U;
+}
+
+
+
+
+
+Array<Float> SDPolUtil::getStokesSlice (Array<Float>& in, const IPosition& start,
+                                        const IPosition& end, const String& stokes)
+{
+   IPosition s(start);
+   IPosition e(end);
+//
+   if (stokes=="I") {
+      s(asap::PolAxis) = 0;
+      e(asap::PolAxis) = 0;
+   } else if (stokes=="Q") {
+      s(asap::PolAxis) = 1;
+      e(asap::PolAxis) = 1;
+   } else if (stokes=="U") {
+      s(asap::PolAxis) = 2;
+      e(asap::PolAxis) = 2;
+   } else if (stokes=="V") {
+      s(asap::PolAxis) = 3;
+      e(asap::PolAxis) = 3;
+   }
+//
+   return in(s,e);
+}
+ 
+
+Array<Float> SDPolUtil::circularPolarizationFromStokes (Array<Float>& I,
+                                                        Array<Float>& V, 
+                                                        Bool doRR)
+//
+// We use the convention
+//  I = (RR+LL)  // definition changed
+//
+{
+   if (doRR) {
+     return (I + V)/Float(2.0);
+   } else {
+     return (I - V)/Float(2.0);
+   }
+}
+
+Stokes::StokesTypes SDPolUtil::convertStokes(Int val, Bool toStokes, Bool linear)
+{   
+   Stokes::StokesTypes stokes = Stokes::Undefined;
+   if (toStokes) {
+      if (val==0) {
+          stokes = Stokes::I;
+      } else if (val==1) {
+         stokes = Stokes::Q;
+      } else if (val==2) {
+         stokes = Stokes::U;
+      } else if (val==3) {
+         stokes = Stokes::V;
+      }   
+   } else if (linear) {
+      if (val==0) {
+         stokes = Stokes::XX;
+      } else if (val==1) {
+         stokes = Stokes::YY;
+      } else if (val==2) {
+         stokes = Stokes::XY;         // Real(XY)
+      } else if (val==3) {
+         stokes = Stokes::XY;         // Imag(XY)
+      }
+   } else {
+      if (val==0) {
+         stokes = Stokes::RR;
+      } else if (val==1) {
+         stokes = Stokes::LL;
+      } else if (val==2) {
+         stokes = Stokes::RL;
+      } else if (val==3) {
+         stokes = Stokes::RL;
+      }
+   }
+//
+   return stokes;
+}
+
+
+
+String SDPolUtil::polarizationLabel (uInt polIdx, Bool linear, Bool stokes, Bool linPol)
+{   
+   Stokes::StokesTypes type = Stokes::Undefined;
+   if (stokes) {
+      switch (polIdx) {
+         case 0:
+           {
+              type = Stokes::I;
+           }
+           break;
+         case 1:
+           {
+              if (linPol) {
+                 type = Stokes::Plinear;
+              } else {
+                 type = Stokes::Q;
+              }
+           }
+           break;
+         case 2:
+           {
+              if (linPol) {
+                 type = Stokes::Pangle;
+              } else {
+                 type = Stokes::U;
+              }
+           }
+           break;
+         case 3:
+           {
+              type = Stokes::V;
+           }
+           break;
+         default:  
+           {
+               throw(AipsError("Unknown Stokes type"));
+           }
+      }
+   } else {
+      if (linear) {
+         switch (polIdx) {
+            case 0:
+              {
+                 type = Stokes::XX;
+              }
+              break;
+            case 1:
+              {
+                 type = Stokes::YY;
+              }
+              break;
+            case 2:
+              {
+                 type = Stokes::XY;              // Really Real(XY)
+                 return String("Real(XY)");
+              }
+              break;
+            case 3:
+              {
+                 type = Stokes::YX;              // Really Imag(XY)
+                 return String("Imag(XY)");
+              }
+              break;
+            default:  
+              {
+                  throw(AipsError("Unknown linear polarization type"));
+              }
+         }
+      } else {  
+         switch (polIdx) {
+            case 0:
+              {
+                 type = Stokes::RR;
+              }
+              break;
+            case 1:
+              {
+                 type = Stokes::LL;
+              }
+	      break;
+            case 2:
+              {
+                 type = Stokes::RL;               // Really Real(RL)
+                 return String("Real(RL)");
+              }
+              break;
+            case 3:
+              {
+                 type = Stokes::LR;               // Really Imag(RL)
+                 return String("Imag(RL)");
+              }
+              break;
+            default:  
+              {
+                  throw(AipsError("Unknown circular polarization type"));
+              }
+         }
+      }
+   }
+//
+   return SDPolUtil::stokesString(type);
+} 
+
+
+
+
+// private
+
+String SDPolUtil::stokesString (Stokes::StokesTypes type)
+{
+  return Stokes::name (type);
+}
+
+
+Array<casa::uChar> SDPolUtil::andArrays (const Array<casa::uChar>& in1,
+                                         const Array<casa::uChar>& in2)
+{
+   Array<uChar> out(in1.shape());
+//
+   Array<uChar>::const_iterator in1Iter;
+   Array<uChar>::const_iterator in2Iter;
+   Array<uChar>::iterator outIter;
+//
+   for (in1Iter=in1.begin(),in2Iter=in2.begin(),outIter=out.begin();
+        in1Iter!=in1.end(); ++in1Iter,++in2Iter,++outIter) {  
+      *outIter = *in1Iter & *in2Iter;
+   }
+   return out;
+}
+
+
+Array<Float> SDPolUtil::extractStokesForWriter (Array<Float>& in, const IPosition& start, const IPosition& end)
+//
+// start/end must already have applied the cursor selection of beam and IF
+// Extract specified Stokes for beam/IF and flip nChan and nPol for bloody SDwriter
+//
+{
+   IPosition shapeIn = in.shape();
+   uInt nChan = shapeIn(asap::ChanAxis);
+   uInt nPol = shapeIn(asap::PolAxis);
+//
+   IPosition shapeOut(2,nChan,nPol);
+   Array<Float> out(shapeOut);
+//
+   Array<Float> sliceRef = in(start,end);                        // Beam and IF now degenerate axes
+   ReadOnlyVectorIterator<Float> itIn(sliceRef, asap::ChanAxis);
+   VectorIterator<Float> itOut(out,0);
+   while (!itIn.pastEnd()) {
+      itOut.vector() = itIn.vector();
+// 
+      itIn.next();
+      itOut.next();
+   }
+//
+   return out;
+}
+
+
+
+
+
Index: tags/asap1/src/SDPol.h
===================================================================
--- tags/asap1/src/SDPol.h	(revision 819)
+++ tags/asap1/src/SDPol.h	(revision 819)
@@ -0,0 +1,259 @@
+//#---------------------------------------------------------------------------
+//# SDPol.h: Polarimetric processing
+//#---------------------------------------------------------------------------
+//# 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 SDPOL_H
+#define SDPOL_H
+
+//# Includes
+#include <casa/aips.h>
+#include <casa/Arrays/Array.h>
+#include <casa/Arrays/ArrayMath.h>
+#include <casa/Arrays/ArrayLogical.h>
+#include <measures/Measures/Stokes.h>
+#include <tables/Tables/BaseMappedArrayEngine.h>
+
+
+namespace asap {
+
+class SDStokesEngine : public casa::BaseMappedArrayEngine<casa::Float, casa::Float>
+{
+  //# Make members of parent class known.
+public:
+  using casa::BaseMappedArrayEngine<casa::Float,casa::Float>::virtualName;
+protected:
+  using casa::BaseMappedArrayEngine<casa::Float,casa::Float>::storedName;
+  using casa::BaseMappedArrayEngine<casa::Float,casa::Float>::table;
+  using casa::BaseMappedArrayEngine<casa::Float,casa::Float>::roColumn;
+  using casa::BaseMappedArrayEngine<casa::Float,casa::Float>::rwColumn;
+
+public:
+    // Construct engine.  The sourveColumnName holds the XX,YY,R(XY),I(XY)
+    // correlations
+    SDStokesEngine (const casa::String& virtualColumnName,
+                    const casa::String& sourceColumnName);
+
+    // Construct from a record specification as created by getmanagerSpec().
+    SDStokesEngine (const casa::Record& spec);
+
+    // Destructor is mandatory.
+    ~SDStokesEngine();
+
+    // Return the type name of the engine (i.e. its class name).
+    virtual casa::String dataManagerType() const;
+
+    // Get the name given to the engine (is the source column name).
+    virtual casa::String dataManagerName() const;
+  
+    // casa::Record a casa::Record containing data manager specifications.
+    virtual casa::Record dataManagerSpec() const;
+
+    // Return the name of the class.
+    // This includes the names of the template arguments.
+    static casa::String className();
+
+   // The engine can access column cells.
+    virtual casa::Bool canAccessArrayColumnCells (casa::Bool& reask) const;
+
+    // Register the class name and the static makeObject "constructor".
+    // This will make the engine known to the table system.
+    // The automatically invoked registration function in DataManReg.cc
+    // contains SDStokesEngine
+    // Any other instantiation of this class must be registered "manually"
+    // (or added to DataManReg.cc).
+    static void registerClass();
+
+    // Non writable
+//    virtual casa::Bool isWritable () const {return casa::False;}
+
+private:
+    // Copy constructor is only used by clone().
+    // (so it is made private).
+    SDStokesEngine (const SDStokesEngine&);
+
+    // Assignment is not needed and therefore forbidden
+    // (so it is made private and not implemented).
+    SDStokesEngine& operator=(const SDStokesEngine&);
+
+    // Clone the engine object.
+    DataManager* clone() const;
+
+    // Initialize the object for a new table.
+    // It defines the keywords containing the engine parameters.
+    void create (casa::uInt initialNrrow);
+
+    // Preparing consists of setting the writable switch and
+    // adding the initial number of rows in case of create.
+    // Furthermore it reads the keywords containing the engine parameters.
+    void prepare();
+
+    // Get an array in the given row.
+    void getArray (casa::uInt rownr, casa::Array<casa::Float>& array);
+
+    // Exception
+    void putArray (casa::uInt rownr, const casa::Array<casa::Float>& array);
+
+    // Compute Stokes parameters
+    void computeOnGet (casa::Array<casa::Float>& array,
+    		     const casa::Array<casa::Float>& target);
+
+    // Get shape
+    virtual casa::IPosition shape (casa::uInt rownr);
+
+    // Convert input to output (virtual) shape
+    casa::IPosition findOutputShape (const casa::IPosition& inputShape) const;
+
+
+public:
+    //*display 4
+    // Define the "constructor" to construct this engine when a
+    // table is read back.
+    // This "constructor" has to be registered by the user of the engine.
+    // If the engine is commonly used, its registration can be added
+    // to the registerAllCtor function in DataManReg.cc. 
+    // That function gets automatically invoked by the table system.
+    static DataManager* makeObject (const casa::String& dataManagerType,
+				    const casa::Record& spec);
+};
+
+
+
+class SDPolUtil
+{
+ public:
+// Convert Q and U to linearly polarized intensity
+  static casa::Array<casa::Float> polarizedIntensity (const casa::Array<casa::Float>& Q,
+                                                      const casa::Array<casa::Float>& U);
+// Convert Q and U to linearly polarized position angle (degrees)
+  static casa::Array<casa::Float> positionAngle (const casa::Array<casa::Float>& Q,
+                                                 const casa::Array<casa::Float>& U);
+
+ // Rotate phase of Complex correlation R+iI by phase (degrees)
+  static void rotatePhase (casa::Array<casa::Float>& R,
+                           casa::Array<casa::Float>& I,
+                           casa::Float phase);
+
+ // Rotate phase of P=(Q+iU) by phase (degrees), but do this by applying 
+ // the corrections to the raw linear polarizations
+  static void rotateLinPolPhase (casa::Array<casa::Float>& C1,
+                                 casa::Array<casa::Float>& C2,
+                                 casa::Array<casa::Float>& C3,
+                                 casa::Array<casa::Float>& I,
+                                 casa::Array<casa::Float>& Q,
+                                 casa::Array<casa::Float>& U,
+                                 casa::Float phase);
+
+// Get Stokes slices from the Array.  Start and End should
+// already be setup to access the Array at the current cursor location
+// (beam, IF, Pol; see SDMemTable).   The desired Stokes
+// is specified in the string from "I", "Q", "U", "V"
+  static casa::Array<casa::Float> getStokesSlice (casa::Array<casa::Float>& input, const casa::IPosition& start,
+                                                  const casa::IPosition& end, const casa::String& stokes);
+
+// Compute Circular polarization RR or LL from I and V
+  static casa::Array<casa::Float> circularPolarizationFromStokes (casa::Array<casa::Float>& I, 
+                                                                  casa::Array<casa::Float>& V,  
+                                                                  casa::Bool doRR);
+
+
+// Compute value for auxilliary spectra when converting to Stokes parameters.
+// This combines the raw correlation values, according to what was combined
+// to actually convert to Stokes parameters.   The output shape may change from 
+// the input shape according to
+// XX or YY          -> I             (1 -> 1)
+// XX,YY             -> I             (2 -> 1)
+// XX,YY,R(XY),I(XY) -> I,Q,U,V       (4 -> 4)
+// 
+// It is meant for tSys (FLoat) and masks (Bool or uChar)
+// The input array must be of shape [nBeam,nIF,nPol,nChan]
+  template <class T>
+  static casa::Array<T> stokesData (casa::Array<T>& rawData, casa::Bool doLinear);
+
+// Find the number of STokes parameters given the input number
+// of raw polarizations
+   static casa::uInt numberStokes (casa::uInt nPol) {casa::uInt nOut = nPol; if (nPol==2) nOut = 1; return nOut;};
+
+// Find the Stokes type for the given polarization axis (0,1,2,3)
+// You can ask for STokes or raw correlations (linear or circular)
+   static casa::Stokes::StokesTypes convertStokes(casa::Int val, casa::Bool toStokes, 
+                                                  casa::Bool linear);
+
+// Return a label for the given polarization index (0-3).   
+   static casa::String polarizationLabel (casa::uInt polIdx, casa::Bool linear, 
+                                          casa::Bool stokes, casa::Bool linPol);
+
+
+// These two functions are explicitly for the SDWriter
+//
+// Compute value for auxilliary spectra (Tsys and flags) when converting to Stokes parameters.
+// This combines the raw correlation values, according to what was combined
+// to actually convert to Stokes parameters.   The output shape may change from 
+// the input shape according to
+// XX or YY          -> I             (1 -> 1)
+// XX,YY             -> I             (2 -> 1)
+// XX,YY,R(XY),I(XY) -> I,Q,U,V       (4 -> 4)
+// 
+// The input array must be of shape [nChan,nPol]  (flags)
+// The input array must be of shape [nPol]        (tSys)
+//
+  static casa::Array<casa::uChar> computeStokesFlagsForWriter (casa::Array<casa::uChar>& rawData, casa::Bool doLinear)
+                                                         {return computeStokesDataForWriter(rawData,doLinear);};
+  static casa::Array<casa::Float> computeStokesTSysForWriter (casa::Array<casa::Float>& rawData, casa::Bool doLinear)
+                                                         {return computeStokesDataForWriter(rawData,doLinear);};
+
+// This function is explicitly for SDWriter. It extracts the Beam and IF slice (specified in start
+// and end - must be length 1 in beam and IF) and flips the pol and channel axes.
+  static casa::Array<casa::Float> extractStokesForWriter (casa::Array<casa::Float>& in, const casa::IPosition& start, 
+                                                          const casa::IPosition& end);
+
+private:
+// Return a label for the given StokesType
+   static casa::String stokesString (casa::Stokes::StokesTypes stokes);
+
+// specializations
+  static casa::Array<casa::Float> andArrays (const casa::Array<casa::Float>& in1,
+                                             const casa::Array<casa::Float>& in2) 
+                                             {return (in1+in2)/casa::Float(2.0);}
+  static casa::Array<casa::Bool> andArrays (const casa::Array<casa::Bool>& in1,
+                                            const casa::Array<casa::Bool>& in2) 
+                                            {return in1&&in2;}
+  static casa::Array<casa::uChar> andArrays (const casa::Array<casa::uChar>& in1,
+                                            const casa::Array<casa::uChar>& in2);
+  
+  template <class T>
+  static casa::Array<T> computeStokesDataForWriter (casa::Array<T>& rawData, casa::Bool doLinear);
+};
+
+
+
+
+
+} // namespace
+
+#endif
Index: tags/asap1/src/SDPol2.cc
===================================================================
--- tags/asap1/src/SDPol2.cc	(revision 819)
+++ tags/asap1/src/SDPol2.cc	(revision 819)
@@ -0,0 +1,247 @@
+//#---------------------------------------------------------------------------
+//# SDPol2.cc: Templated polarimetric functionality
+//#---------------------------------------------------------------------------
+//# 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 "SDPol.h"
+#include "SDDefs.h"
+
+#include <casa/Arrays/Array.h>
+#include <casa/Arrays/ArrayMath.h>
+#include <casa/Arrays/ArrayLogical.h>
+#include <casa/BasicSL/String.h>
+#include <casa/Utilities/Assert.h>
+#include <casa/Utilities/DataType.h>
+#include <casa/Utilities/Assert.h>
+
+
+using namespace casa;
+using namespace asap;
+
+
+template <class T>
+Array<T> SDPolUtil::stokesData (Array<T>& rawData, Bool doLinear)
+//
+// Generate data for each Stokes parameter from the
+// raw flags.  This is a lot of computational work and may
+// not be worth the effort.
+//
+// Designed for use 
+//   Bool or uChar for mask
+//   Float for TSys
+//
+// Input arrays should be of shape
+//    [nBeam,nIF,nPol,nChan]
+//
+{
+   T* t;
+   DataType type = whatType(t);
+   AlwaysAssert(type==TpFloat || type==TpBool || type==TpUChar, AipsError);
+//
+   IPosition shapeIn = rawData.shape();
+   const uInt nDim = shapeIn.nelements();
+   uInt polAxis = 0;
+   AlwaysAssert(nDim==asap::nAxes, AipsError);
+//
+   uInt nPol = shapeIn(asap::PolAxis);
+   Array<T> stokesData;
+//
+   IPosition start(nDim,0);
+   IPosition end(shapeIn-1);
+   IPosition shapeOut = shapeIn;
+//
+   if (doLinear) {
+      if (nPol==1) {
+         stokesData.resize(shapeOut);
+         stokesData = rawData;
+      } else if (nPol==2 || nPol==4) {
+
+// Set shape of output array
+
+         if (nPol==2) {
+            shapeOut(asap::PolAxis) = 1;
+         } else {
+            shapeOut(asap::PolAxis) = 4;
+         }
+         stokesData.resize(shapeOut);
+
+// Get reference slices and assign/compute
+
+         start(asap::PolAxis) = 0;
+         end(asap::PolAxis) = 0;
+         Array<T> M1In = rawData(start,end);
+//
+         start(asap::PolAxis) = 1;
+         end(asap::PolAxis) = 1;
+         Array<T> M2In = rawData(start,end);
+//
+         start(asap::PolAxis) = 0;
+         end(asap::PolAxis) = 0;
+         Array<T> M1Out = stokesData(start,end);         
+         M1Out = SDPolUtil::andArrays (M1In, M2In);         // I
+//
+         if (nPol==4) {   
+            start(asap::PolAxis) = 2;
+            end(asap::PolAxis) = 2;
+            Array<T> M3In = rawData(start,end);
+//
+            start(asap::PolAxis) = 3;
+            end(asap::PolAxis) = 3;
+            Array<T> M4In = rawData(start,end);
+//
+            start(asap::PolAxis) = 1;
+            end(asap::PolAxis) = 1;
+            Array<T> M2Out = stokesData(start,end);
+            M2Out = M1Out;                                  // Q
+//
+            start(asap::PolAxis) = 2;
+            end(asap::PolAxis) = 2;
+            Array<T> M3Out = stokesData(start,end);
+            M3Out = M3In;                                   // U
+//
+            start(asap::PolAxis) = 3;
+            end(asap::PolAxis) = 3;
+            Array<T> M4Out = stokesData(start,end);
+            M4Out = M4In;                                   // V
+         }
+      } else {
+         throw(AipsError("Can only handle 1,2 or 4 polarizations"));
+      }
+   } else {
+      throw (AipsError("Only implemented for Linear polarizations"));
+   }
+//
+   return stokesData;
+}
+
+
+
+template <class T>
+Array<T> SDPolUtil::computeStokesDataForWriter (Array<T>& rawData, Bool doLinear)
+//
+// Generate data for each Stokes parameter from the
+// raw flags.  This is a lot of computational work and may
+// not be worth the effort.  This function is specifically
+// for the SDWriter 
+//
+// Designed for use 
+//   Bool or uChar for mask
+//   Float for TSys
+//
+// Input arrays should be of shape
+//    [nChan,nPol]   Bool/uChar
+//    [nPol]         Float      
+//
+{
+   T* t;
+   DataType type = whatType(t);
+   AlwaysAssert(type==TpFloat || type==TpBool || type==TpUChar, AipsError);
+//
+   IPosition shapeIn = rawData.shape();
+   const uInt nDim = shapeIn.nelements();
+   uInt polAxis = 0;
+   if (type==TpFloat) {
+      AlwaysAssert(nDim==1,AipsError);
+   } else {
+      AlwaysAssert(nDim==2,AipsError);
+      polAxis = 1;
+   }
+//
+   uInt nPol = shapeIn(polAxis);
+   Array<T> stokesData;
+//
+   IPosition start(nDim,0);
+   IPosition end(shapeIn-1);
+   IPosition shapeOut = shapeIn;
+//
+   if (doLinear) {
+      if (nPol==1) {
+         stokesData.resize(shapeOut);
+         stokesData = rawData;
+      } else if (nPol==2 || nPol==4) {
+
+// Set shape of output array
+
+         if (nPol==2) {
+            shapeOut(polAxis) = 1;
+         } else {
+            shapeOut(polAxis) = 4;
+         }
+         stokesData.resize(shapeOut);
+
+// Get reference slices and assign/compute
+
+         start(polAxis) = 0;
+         end(polAxis) = 0;
+         Array<T> M1In = rawData(start,end);
+//
+         start(polAxis) = 1;
+         end(polAxis) = 1;
+         Array<T> M2In = rawData(start,end);
+//
+         start(polAxis) = 0;
+         end(polAxis) = 0;
+         Array<T> M1Out = stokesData(start,end);         
+         M1Out = SDPolUtil::andArrays (M1In, M2In);         // I
+//
+         if (nPol==4) {   
+            start(polAxis) = 2;
+            end(polAxis) = 2;
+            Array<T> M3In = rawData(start,end);
+//
+            start(polAxis) = 3;
+            end(polAxis) = 3;
+            Array<T> M4In = rawData(start,end);
+//
+            start(polAxis) = 1;
+            end(polAxis) = 1;
+            Array<T> M2Out = stokesData(start,end);
+            M2Out = M1Out;                                  // Q
+//
+            start(polAxis) = 2;
+            end(polAxis) = 2;
+            Array<T> M3Out = stokesData(start,end);
+            M3Out = M3In;                                   // U
+//
+            start(polAxis) = 3;
+            end(polAxis) = 3;
+            Array<T> M4Out = stokesData(start,end);
+            M4Out = M4In;                                   // V
+         }
+      } else {
+         throw(AipsError("Can only handle 1,2 or 4 polarizations"));
+      }
+   } else {
+      throw (AipsError("Only implemented for Linear polarizations"));
+   }
+//
+   return stokesData;
+}
+
+
Index: tags/asap1/src/SDReader.cc
===================================================================
--- tags/asap1/src/SDReader.cc	(revision 819)
+++ tags/asap1/src/SDReader.cc	(revision 819)
@@ -0,0 +1,324 @@
+//#---------------------------------------------------------------------------
+//# SDReader.cc: A class to read single dish spectra from SDFITS, RPFITS
+//#---------------------------------------------------------------------------
+//# 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/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 <atnf/PKSIO/PKSreader.h>
+
+#include "SDReader.h"
+#include "SDDefs.h"
+#include "SDAttr.h"
+
+using namespace casa;
+using namespace asap;
+
+SDReader::SDReader() :
+  reader_(0),
+  header_(0),
+  frequencies_(0),
+  table_(new SDMemTable()),
+  haveXPol_(False)
+{
+  cursor_ = 0;
+}
+SDReader::SDReader(const std::string& filename,
+                   int whichIF, int whichBeam) :
+  reader_(0),
+  header_(0),
+  frequencies_(0),
+  table_(new SDMemTable()),
+  haveXPol_(False)
+{
+  cursor_ = 0;
+  open(filename, whichIF, whichBeam);
+}
+
+SDReader::SDReader(CountedPtr<SDMemTable> tbl) :
+  reader_(0),
+  header_(0),
+  table_(tbl),
+  haveXPol_(False)
+{
+  cursor_ = 0;
+}
+
+SDReader::~SDReader() {
+  if (reader_) delete reader_;
+  if (header_) delete header_;
+  if (frequencies_) delete frequencies_;
+}
+
+void SDReader::reset() {
+  cursor_ = 0;
+  table_ = new SDMemTable();
+  open(filename_,ifOffset_, beamOffset_);
+}
+
+
+void SDReader::close() {
+  //cerr << "disabled" << endl;
+}
+
+void SDReader::open(const std::string& filename,
+                    int whichIF, int whichBeam) {
+  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;
+  if ((reader_ = getPKSreader(inName, 0, False, format, beams, nIF_,
+                              nChan_, nPol_, haveBase, haveSpectra,
+                              haveXPol_)) == 0)  {
+    throw(AipsError("Creation of PKSreader failed"));
+  }
+  if (!haveSpectra) {
+    delete reader_;
+    reader_ = 0;
+    throw(AipsError("No spectral data in file."));
+    return;
+  }
+
+  nBeam_ = beams.nelements();
+  // Get basic parameters.
+  if (haveXPol_) {
+    pushLog("Cross polarization present");
+    nPol_ += 2;                          // Convert Complex -> 2 Floats
+  }
+
+  if (header_) delete header_;
+  header_ = new SDHeader();
+  header_->nchan = nChan_;
+  header_->npol = nPol_;
+  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;
+    throw(AipsError("Failed to get header."));
+    return;
+  }
+  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;
+  }
+
+  // Determine Telescope and set brightness unit
+
+  Bool throwIt = False;
+  Instrument inst = SDAttr::convertInstrument(header_->antennaname, throwIt);
+  header_->fluxunit = "Jy";
+  if (inst==ATMOPRA || inst==TIDBINBILLA) {
+     header_->fluxunit = "K";
+  }
+
+  header_->nif = nIF_;
+  header_->epoch = "UTC";
+
+  // Apply selection criteria.
+
+  Vector<Int> ref;
+  Vector<Bool> beamSel(nBeam_,True);
+  Vector<Bool> IFsel(nIF_,True);
+
+  ifOffset_ = 0;
+  if (whichIF>=0) {
+    if (whichIF>=0 && whichIF<nIF_) {
+       IFsel = False;
+       IFsel(whichIF) = True;
+       header_->nif = 1;
+       nIF_ = 1;
+       ifOffset_ = whichIF;
+    } else {
+       throw(AipsError("Illegal IF selection"));
+    }
+  }
+
+  beamOffset_ = 0;
+  if (whichBeam>=0) {
+     if (whichBeam>=0 && whichBeam<nBeam_) {
+        beamSel = False;
+        beamSel(whichBeam) = True;
+        header_->nbeam = 1;
+        nBeam_ = 1;
+        beamOffset_ = whichBeam;
+     } else {
+       throw(AipsError("Illegal Beam selection"));
+     }
+  }
+  Vector<Int> start(nIF_, 1);
+  Vector<Int> end(nIF_, 0);
+  reader_->select(beamSel, IFsel, start, end, ref, True, haveXPol_);
+  table_->putSDHeader(*header_);
+
+  if (frequencies_) delete frequencies_;
+  frequencies_ = new SDFrequencyTable();
+  frequencies_->setRefFrame(header_->freqref);
+  frequencies_->setBaseRefFrame(header_->freqref);
+  frequencies_->setRestFrequencyUnit("Hz");
+  frequencies_->setEquinox(header_->equinox);
+}
+
+int SDReader::read(const std::vector<int>& seq) {
+  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;
+  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;
+  uInt n = seq.size();
+
+
+  uInt stepsize = header_->nif*header_->nbeam;
+  uInt seqi = 0;
+  Bool getAll = False;
+
+  if (seq[n-1] == -1) getAll = True;
+  while ( (cursor_ <= seq[n-1]) || getAll) {
+    // only needs to be create if in seq
+    SDContainer sc(header_->nbeam,header_->nif,header_->npol,header_->nchan);
+    // iterate over one correlator integration cycle = nBeam*nIF
+    for (uInt row=0; row < stepsize; row++) {
+      // stepsize as well
+      // spectra(nChan,nPol)!!!
+      status = reader_->read(scanNo, cycleNo, mjd, interval, fieldName,
+                             srcName, srcDir, srcPM, srcVel, 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);
+
+      // Make sure beam/IF numbers are 0-relative - dealing with
+      // possible IF or Beam selection
+      beamNo = beamNo - beamOffset_ - 1;
+      IFno = IFno - ifOffset_ - 1;
+
+      if (status) {
+        if (status == -1) {
+          // EOF.
+          if (row > 0 && row < stepsize-1)
+            pushLog("incomplete scan data.\n Probably means not all Beams/IFs/Pols within a scan are present.");
+
+          // flush frequency table
+          table_->putSDFreqTable(*frequencies_);
+          return status;
+        }
+      }
+      // if in the given list
+      if (cursor_ == seq[seqi] || getAll) {
+        // add integration cycle
+        if (row==0) {
+          //add invariant info: scanNo, mjd, interval, fieldName,
+          //srcName, azimuth, elevation, parAngle, focusAxi, focusTan,
+          //focusRot, temperature, pressure, humidity, windSpeed,
+          //windAz  srcDir, srcPM, srcVel
+          sc.timestamp = mjd;
+          sc.interval = interval;
+          sc.sourcename = srcName;
+          sc.fieldname = fieldName;
+          sc.azimuth = azimuth;
+          sc.elevation = elevation;
+        }
+        // add specific info
+        // refPix = nChan/2+1 in  1-rel Integer arith.!
+        Int refPix = header_->nchan/2;       // 0-rel
+        uInt freqID = frequencies_->addFrequency(refPix, refFreq, freqInc);
+        uInt restFreqID = frequencies_->addRestFrequency(restFreq);
+
+        sc.setFrequencyMap(freqID, IFno);
+        sc.setRestFrequencyMap(restFreqID, IFno);
+
+        sc.tcal[0] = tcal[0];sc.tcal[1] = tcal[1];
+        sc.tcaltime = tcalTime;
+        sc.parangle = parAngle;
+        sc.refbeam = -1; //nbeams == 1
+        if (nBeam_ > 1) // circumvent a bug "asap0000" in read which
+                        // returns a random refbema number on multiple
+                        // reads
+          sc.refbeam = refBeam-1;//make it 0-based;
+        sc.scanid = scanNo-1;//make it 0-based
+        if (haveXPol_) {
+           sc.setSpectrum(spectra, xPol, beamNo, IFno);
+           sc.setFlags(flagtra,  beamNo, IFno, True);
+        } else {
+           sc.setSpectrum(spectra, beamNo, IFno);
+           sc.setFlags(flagtra,  beamNo, IFno, False);
+        }
+        sc.setTsys(tsys, beamNo, IFno, haveXPol_);
+        sc.setDirection(direction, beamNo);
+      }
+    }
+
+    if (cursor_ == seq[seqi] || getAll) {
+      // insert container into table/list
+      table_->putSDContainer(sc);
+      seqi++;// next in list
+    }
+    cursor_++;// increment position in file
+
+  }
+  table_->putSDFreqTable(*frequencies_);
+  return status;
+}
Index: tags/asap1/src/SDReader.h
===================================================================
--- tags/asap1/src/SDReader.h	(revision 819)
+++ tags/asap1/src/SDReader.h	(revision 819)
@@ -0,0 +1,93 @@
+//#---------------------------------------------------------------------------
+//# SDReader.h: A class to read single dish spectra from SDFITS, RPFITS
+//#---------------------------------------------------------------------------
+//# 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 SDREADER_H
+#define SDREADER_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 "SDMemTable.h"
+#include "SDContainer.h"
+#include "SDLog.h"
+
+class PKSreader;
+
+namespace asap {
+
+class SDReader : public SDLog {
+public:
+  SDReader();
+  SDReader(const std::string& filename, 
+           int whichIF=-1, int whichBeam=-1);
+  SDReader(casa::CountedPtr<SDMemTable> tbl);
+  virtual ~SDReader();
+
+  void open(const std::string& filename,
+            int whichIF=-1,
+            int whichBeam=-1);
+  void close();
+  int read(const std::vector<int>& seq);
+
+  casa::CountedPtr<SDMemTable> getTable() const { return table_;}
+
+  void reset();
+
+  std::vector<int> pseudoHeader() const {
+    std::vector<int> v;
+    v.push_back(nBeam_);v.push_back(nIF_);
+    v.push_back(nPol_);v.push_back(nChan_);
+    return v;
+  }
+
+
+protected:
+  
+private:
+  casa::Int nBeam_,nIF_,nPol_,nChan_;
+  PKSreader* reader_;  
+  SDHeader* header_;
+  SDFrequencyTable* frequencies_;
+  casa::CountedPtr<SDMemTable> table_;
+  casa::String filename_;
+  casa::uInt cursor_;
+  casa::Double timestamp_;
+  casa::uInt beamOffset_, ifOffset_;
+  casa::Bool haveXPol_;
+};
+
+}// namespace
+#endif
Index: tags/asap1/src/SDReaderWrapper.h
===================================================================
--- tags/asap1/src/SDReaderWrapper.h	(revision 819)
+++ tags/asap1/src/SDReaderWrapper.h	(revision 819)
@@ -0,0 +1,55 @@
+//#---------------------------------------------------------------------------
+//# SDReaderWrapper.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 SDREADERWRAPPER_H
+#define SDREADERWRAPPER_H
+
+#include <vector>
+#include <string>
+
+#include "SDMemTableWrapper.h"
+#include "SDReader.h"
+
+namespace asap {
+
+class SDReaderWrapper : public SDReader {
+public:
+  SDReaderWrapper() {;}
+  SDReaderWrapper(const std::string& filename, int ifSel=-1, int beamSel=-1) : 
+     SDReader(filename,ifSel,beamSel) {;}
+  SDReaderWrapper(SDMemTableWrapper tbl) :
+    SDReader(tbl.getCP()){;}
+  SDMemTableWrapper getSDMemTable() const {
+    return SDMemTableWrapper(getTable());
+  }
+};
+
+} // namespace
+#endif
Index: tags/asap1/src/SDTemplates.cc
===================================================================
--- tags/asap1/src/SDTemplates.cc	(revision 819)
+++ tags/asap1/src/SDTemplates.cc	(revision 819)
@@ -0,0 +1,103 @@
+//#---------------------------------------------------------------------------
+//# SDTemplates.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 "SDMemTable.h"
+
+#include <casa/aips.h>
+#include <casa/namespace.h>
+#include <casa/Exceptions/Error.cc>
+//#include <casa/Exceptions/Error2.cc>
+#include <casa/Utilities/CountedPtr.cc>
+//#include <casa/Utilities/CountedPtr2.cc>
+
+namespace asap {
+  template class casa::CountedConstPtr<SDMemTable>;
+  template class casa::CountedPtr<SDMemTable>;
+  template class casa::PtrRep<SDMemTable>;
+  template class casa::SimpleCountedConstPtr<SDMemTable>;
+  template class casa::SimpleCountedPtr<SDMemTable>;
+}
+
+#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>
+
+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 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.cc"
+#include "SDPol2.cc"
+namespace mathutil {
+  template void hanning(Vector<Float>&, Vector<Bool>&,
+			const Vector<Float>&, 
+			const Vector<Bool>&, 
+			Bool, Bool);
+  template uInt addEntry(Vector<uInt>&, uInt);
+  template void extendLastArrayAxis(Array<Int>&, const Array<Int>&,
+				   const Int& initVal);
+}
+template Array<Bool> SDPolUtil::stokesData (Array<Bool>& dataIn, Bool);
+template Array<Float> SDPolUtil::computeStokesDataForWriter(Array<Float>& dataIn, Bool);
+template Array<uChar> SDPolUtil::computeStokesDataForWriter(Array<uChar>& dataIn, Bool);
+
Index: tags/asap1/src/SDWriter.cc
===================================================================
--- tags/asap1/src/SDWriter.cc	(revision 819)
+++ tags/asap1/src/SDWriter.cc	(revision 819)
@@ -0,0 +1,315 @@
+//#---------------------------------------------------------------------------
+//# SDWriter.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/ArrayColumn.h>
+
+
+
+
+#include "SDContainer.h"
+#include "SDMemTable.h"
+#include "SDWriter.h"
+#include "SDFITSImageWriter.h"
+#include "SDAsciiWriter.h"
+#include "SDPol.h"
+
+using namespace casa;
+using namespace asap;
+
+//--------------------------------------------------------- SDWriter::SDWriter
+
+// Default constructor.
+
+SDWriter::SDWriter(const std::string &format)
+{
+  cFormat = format;
+//
+  String t(cFormat);
+  t.upcase();
+  if (t== "MS2") {
+    cWriter = new PKSMS2writer();
+  } else if (t== "SDFITS") {
+    cWriter = new PKSSDwriter();
+  } else if (t== "FITS") {
+    cWriter = 0;
+  } else if (t== "ASCII") {
+    cWriter = 0;
+  } else {
+    throw (AipsError("Unrecognized Format"));
+  }
+}
+
+//-------------------------------------------------------- SDWriter::~SDWriter
+
+// Destructor.
+
+SDWriter::~SDWriter()
+{
+   if (cWriter) {
+     delete cWriter;
+   }
+}
+
+//-------------------------------------------------------- SDWriter::setFormat
+
+// Reset the output format.
+
+Int SDWriter::setFormat(const std::string &format)
+{
+  if (format != cFormat) {
+    if (cWriter) delete cWriter;
+  }
+//
+  cFormat = format;
+  String t(cFormat);
+  t.upcase();
+  if (t== "MS2") {
+    cWriter = new PKSMS2writer();
+  } else if (t== "SDFITS") {
+    cWriter = new PKSSDwriter();
+  } else if (t== "FITS") {
+    cWriter = 0;
+  } else if (t== "ASCII") {
+    cWriter = 0;
+  } else {
+    throw (AipsError("Unrecognized Format"));
+  }
+  return 0;
+}
+
+//------------------------------------------------------------ SDWriter::write
+
+// Write an SDMemTable to file in the desired format, closing the file when
+// finished.
+
+Int SDWriter::write(const CountedPtr<SDMemTable> in,
+                    const std::string &filename, Bool toStokes)
+{
+
+// Image FITS
+
+  if (cFormat=="FITS") {
+     Bool verbose = True;
+     SDFITSImageWriter iw;
+     if (iw.write(*in, filename, verbose, toStokes)) {
+        return 0;
+     } else {
+        return 1;
+     }
+  } else if (cFormat=="ASCII") {
+     SDAsciiWriter iw;
+     if (iw.write(*in, filename, toStokes)) {
+        return 0;
+     } else {
+        return 1;
+     }
+  }
+
+// MS or SDFITS
+
+  // Extract the header from the table.
+  SDHeader hdr = in->getSDHeader();
+  const Int nPol  = hdr.npol;
+  const Int nChan = hdr.nchan;
+
+// Get Freq table
+
+  SDFrequencyTable sdft = in->getSDFreqTable();
+  Vector<Double> restFreqs;
+  String restFreqUnit;
+  sdft.restFrequencies(restFreqs, restFreqUnit);
+  Double restFreq = 0.0;
+  if (restFreqs.nelements()>0) {
+     Quantum<Double> rF(restFreqs(0), Unit(restFreqUnit));
+     restFreq = rF.getValue(Unit("Hz"));
+  }
+
+// Table columns
+
+  const Table table = in->table();
+  ROArrayColumn<uInt> freqIDCol(table, "FREQID");
+  Vector<uInt> freqIDs;
+
+  // Create the output file and write static data.
+  Int status;
+  if (status = cWriter->create(filename, hdr.observer, hdr.project,
+                               hdr.antennaname, hdr.antennaposition,
+                               hdr.obstype, hdr.equinox, hdr.freqref,
+                               nChan, nPol, False, False)) {
+    throw(AipsError("Failed to create output file"));
+  }
+
+  Int scanNo = -1;
+  Int cycleNo;
+  Double mjd0 = 0.0;
+//
+  Array<Float> spectra, tSys, stokes;
+  Array<uChar> flags;
+  Bool doLinear = True;
+//
+  Int count = 0;
+  for (Int iRow = 0; iRow < in->nRow(); iRow++) {
+    // Extract the next integration from the table.
+    SDContainer sd = in->getSDContainer(iRow);
+    if (sd.scanid != scanNo) {
+      scanNo = sd.scanid;
+      mjd0 = sd.timestamp;
+      cycleNo = 1;
+    } else if (fabs(sd.timestamp-mjd0) > sd.interval) {
+      cycleNo++;
+    }
+
+    // Get FreqID vector
+    freqIDCol.get(iRow, freqIDs);
+
+    // Get Stokes array (all beams/IFs/Stokes).  Its not in SDContainer
+    if (toStokes) {
+       stokes = in->getStokesSpectrum(iRow,-1,-1);
+    }
+
+    IPosition start(asap::nAxes,0);
+    IPosition end(stokes.shape()-1);
+
+    // Write it out beam by beam.
+    for (Int iBeam = 0; iBeam < hdr.nbeam; iBeam++) {
+       start(asap::BeamAxis) = iBeam;
+       end(asap::BeamAxis) = iBeam;
+
+      // Write it out IF by IF.
+      for (Int iIF = 0; iIF < hdr.nif; iIF++) {
+        start(asap::IFAxis) = iIF;
+        end(asap::IFAxis) = iIF;
+        uInt freqID = freqIDs(iIF);
+
+        // None of these are stored in SDMemTable by SDReader.
+        //String          fieldName = "";
+        //Vector<Double>  srcDir(2, 0.0);
+        Vector<Double>  srcPM(2, 0.0);
+        Double          srcVel = 0.0;
+
+// The writer will assume refPix = nChan/2 (0-rel).  So recompute
+// the frequency at this location. 
+
+        Double          cdelt = sdft.increment(freqID);
+        Double          crval = sdft.referenceValue(freqID);
+        Double          crpix = sdft.referencePixel(freqID);
+        Double          pixel = nChan/2;
+        Double          refFreqNew = (pixel-crpix)*cdelt + crval;
+//
+        //Vector<Float>   tcal(2, 0.0f);
+        //String          tcalTime = "";
+        //Float           azimuth = 0.0f;
+        //Float           elevation = 0.0f;
+        //Float           parAngle = 0.0f;
+        Float           focusAxi = 0.0f;
+        Float           focusTan = 0.0f;
+        Float           focusRot = 0.0f;
+        Float           temperature = 0.0f;
+        Float           pressure = 0.0f;
+        Float           humidity = 0.0f;
+        Float           windSpeed = 0.0f;
+        Float           windAz = 0.0f;
+        //Int             refBeam = 0;
+        //Vector<Double>  direction(2, 0.0);
+        Vector<Double>  scanRate(2, 0.0);
+        Vector<Float>   sigma(nPol, 0.0f);
+        Vector<Float>   calFctr(nPol, 0.0f);
+        Matrix<Float>   baseLin(nPol,2, 0.0f);
+        Matrix<Float>   baseSub(nPol,9, 0.0f);
+        Complex         xCalFctr;
+        Vector<Complex> xPol;
+
+// Get data.  
+
+        if (toStokes) {
+
+// Big pain in the ass because
+//  1) Stokes vector may have less polarizations than input
+//  2) Stokes column virtual and not in SDContainer
+//  3) Tsys and FLags already selected for IF and Beam 
+//  3) Have to flip order
+
+           Array<Float> t = sd.getTsys(iBeam,iIF);
+           tSys = SDPolUtil::computeStokesTSysForWriter (t, doLinear);
+           Array<uChar> t2 = sd.getFlags(iBeam,iIF);
+           flags = SDPolUtil::computeStokesFlagsForWriter (t2, doLinear);
+           spectra = SDPolUtil::extractStokesForWriter (stokes,start,end);
+        } else {
+           tSys = sd.getTsys(iBeam,iIF);
+           flags = sd.getFlags(iBeam,iIF);
+           spectra = sd.getSpectrum(iBeam,iIF);        
+        }
+//
+        if (status = cWriter->write(sd.scanid+1, cycleNo, sd.timestamp,
+                                    sd.interval, sd.fieldname, sd.sourcename,
+                                    sd.getDirection(iBeam),
+                                    srcPM, srcVel, iIF+1, refFreqNew,
+                                    nChan*abs(cdelt), cdelt, restFreq, sd.tcal,
+                                    sd.tcaltime, sd.azimuth, sd.elevation, 
+				    sd.parangle,
+                                    focusAxi, focusTan, focusRot, temperature,
+                                    pressure, humidity, windSpeed, windAz,
+                                    sd.refbeam, iBeam+1,
+                                    sd.getDirection(iBeam),
+                                    scanRate,
+                                    tSys, sigma, calFctr,
+                                    baseLin, baseSub,
+                                    spectra, flags,
+                                    xCalFctr, xPol)) {
+          cerr << "Error writing output file." << endl;
+          return 1;
+        }
+
+        count++;
+      }
+    }
+  }
+  ostringstream oss;
+  oss << "SDWriter: wrote " << count << " rows to " << filename << endl;
+  pushLog(String(oss));
+  cWriter->close();
+
+  return 0;
+}
+
+
Index: tags/asap1/src/SDWriter.h
===================================================================
--- tags/asap1/src/SDWriter.h	(revision 819)
+++ tags/asap1/src/SDWriter.h	(revision 819)
@@ -0,0 +1,65 @@
+//#---------------------------------------------------------------------------
+//# SDWriter.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 SDWRITER_H
+#define SDWRITER_H
+
+#include <string>
+
+#include <casa/aips.h>
+#include <casa/Utilities/CountedPtr.h>
+
+#include <SDMemTable.h>
+#include <SDMemTableWrapper.h>
+
+#include <SDLog.h>
+
+class PKSwriter;
+
+namespace asap {
+
+class SDWriter : public SDLog {
+public:
+  SDWriter(const string &format = "SDFITS");
+  ~SDWriter();
+
+// Format can be "SDFITS", "FITS", "MS2" or "ASCII"
+// Stokes conversion available for FITS and ASCII at present
+  casa::Int setFormat(const string &format = "SDFITS");
+  casa::Int write(const casa::CountedPtr<SDMemTable> table,
+            const string &filename, casa::Bool toStokes);
+
+private:
+  std::string     cFormat;
+  PKSwriter *cWriter;
+};
+
+}// namespace
+#endif
Index: tags/asap1/src/SDWriterWrapper.h
===================================================================
--- tags/asap1/src/SDWriterWrapper.h	(revision 819)
+++ tags/asap1/src/SDWriterWrapper.h	(revision 819)
@@ -0,0 +1,53 @@
+//#---------------------------------------------------------------------------
+//# SDWriterWrapper.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 SDWRITERWRAPPER_H
+#define SDWRITERWRAPPER_H
+
+#include <vector>
+#include <string>
+
+#include "SDWriter.h"
+
+namespace asap {
+
+class SDMemTableWrapper;
+
+class SDWriterWrapper : public SDWriter {
+public:
+  SDWriterWrapper(const string &format = "SDFITS") : SDWriter(format) {;}
+
+  casa::Int write(const SDMemTableWrapper &table, const string &filename, bool toStokes) {
+    return SDWriter::write(table.getCP(), filename, toStokes);
+  }
+};
+
+} // namespace asap
+#endif
Index: tags/asap1/src/pyconversions.h
===================================================================
--- tags/asap1/src/pyconversions.h	(revision 819)
+++ tags/asap1/src/pyconversions.h	(revision 819)
@@ -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/asap1/src/python_SD.cc
===================================================================
--- tags/asap1/src/python_SD.cc	(revision 819)
+++ tags/asap1/src/python_SD.cc	(revision 819)
@@ -0,0 +1,82 @@
+//#---------------------------------------------------------------------------
+//# python_SD.cc: python module for single dish package
+//#---------------------------------------------------------------------------
+//# 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 "pyconversions.h"
+#include "python_SD.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_SDMemTable();
+  asap::python::python_SDReader();
+  asap::python::python_SDWriter();
+  asap::python::python_SDMath();
+  asap::python::python_SDFitter();
+  asap::python::python_SDFitTable();
+  asap::python::python_SDLineFinder();
+  asap::python::python_SDLog();
+
+  register_exception_translator<casa::AipsError>(&asap::python::translate_ex);
+
+  std_vector_to_tuple < int > ();
+  from_python_sequence < std::vector < int >,
+    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/asap1/src/python_SD.h
===================================================================
--- tags/asap1/src/python_SD.h	(revision 819)
+++ tags/asap1/src/python_SD.h	(revision 819)
@@ -0,0 +1,51 @@
+//#---------------------------------------------------------------------------
+//# python_SD.h: python module for single dish package
+//#---------------------------------------------------------------------------
+//# 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_SD_H
+#define PYTHON_SD_H
+
+class casa::AipsError;
+
+namespace asap {
+  namespace python {
+    void translate_ex(const casa::AipsError& e);
+    void python_SDMemTable();
+    void python_SDReader();
+    void python_SDWriter();
+    void python_SDMath();
+    void python_SDFitter();
+    void python_SDFitTable();
+    void python_SDLineFinder();
+    void python_SDLog();
+
+  } // python
+} //asap
+
+#endif
Index: tags/asap1/src/python_SDFitTable.cc
===================================================================
--- tags/asap1/src/python_SDFitTable.cc	(revision 819)
+++ tags/asap1/src/python_SDFitTable.cc	(revision 819)
@@ -0,0 +1,54 @@
+//#---------------------------------------------------------------------------
+//# python_SDFitTable.cc: python exposure of c++ SDFitTable 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 "SDFitTable.h"
+
+using namespace boost::python;
+
+namespace asap {
+  namespace python {
+
+    void python_SDFitTable() {
+      class_<SDFitTable>("sdfit")
+        .def( init <> () )
+	.def( init < const SDFitTable& > () )
+        .def("__len__", &SDFitTable::STLlength)
+        .def("getfixedparameters", &SDFitTable::getSTLParameterMask)
+        .def("getparameters", &SDFitTable::getSTLParameters)
+        .def("getfunctions", &SDFitTable::getSTLFunctions)
+	.def("getcomponents", &SDFitTable::getSTLComponents)
+	.def("getframeinfo", &SDFitTable::getSTLFrameInfo)
+      ;
+    };
+
+  } //namespace python
+} // namespace asap
Index: tags/asap1/src/python_SDFitter.cc
===================================================================
--- tags/asap1/src/python_SDFitter.cc	(revision 819)
+++ tags/asap1/src/python_SDFitter.cc	(revision 819)
@@ -0,0 +1,62 @@
+//#---------------------------------------------------------------------------
+//# python_SDFitter.cc: python exposure of c++ SDFitter 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 "SDFitter.h"
+
+using namespace boost::python;
+
+namespace asap {
+  namespace python {
+
+    void python_SDFitter() {
+      class_<SDFitter>("fitter")
+        .def( init <> () )
+        .def("setexpression", &SDFitter::setExpression)
+        .def("setdata", &SDFitter::setData)
+        .def("getresidual", &SDFitter::getResidual)
+        .def("getfit", &SDFitter::getFit)
+        .def("getfixedparameters", &SDFitter::getFixedParameters)
+        .def("setfixedparameters", &SDFitter::setFixedParameters)
+        .def("getparameters", &SDFitter::getParameters)
+        .def("setparameters", &SDFitter::setParameters)
+        .def("getestimate", &SDFitter::getEstimate)
+        .def("estimate", &SDFitter::computeEstimate)
+        .def("geterrors", &SDFitter::getErrors)
+        .def("getchi2", &SDFitter::getChisquared)
+        .def("reset", &SDFitter::reset)
+        .def("fit", &SDFitter::fit)
+        .def("evaluate", &SDFitter::evaluate)
+      ;
+    };
+
+  } //namespace python
+} // namespace asap
Index: tags/asap1/src/python_SDLineFinder.cc
===================================================================
--- tags/asap1/src/python_SDLineFinder.cc	(revision 819)
+++ tags/asap1/src/python_SDLineFinder.cc	(revision 819)
@@ -0,0 +1,51 @@
+//#---------------------------------------------------------------------------
+//# python_SDLineFinder.cc: python exposure of C++ SDLineFinder 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 "SDLineFinder.h"
+
+using namespace boost::python;
+
+namespace asap {
+  namespace python {
+     void python_SDLineFinder() {
+       class_<SDLineFinder>("linefinder")
+         .def( init <> () )
+	 .def("setoptions",&SDLineFinder::setOptions)
+         .def("setscan",&SDLineFinder::setScan)
+         .def("findlines",&SDLineFinder::findLines)
+         .def("getmask",&SDLineFinder::getMask)
+         .def("getlineranges",&SDLineFinder::getLineRanges)
+         .def("getlinerangesinchannels",&SDLineFinder::getLineRangesInChannels)
+       ;
+     };
+  } // namespace python
+} // namespace asap
Index: tags/asap1/src/python_SDLog.cc
===================================================================
--- tags/asap1/src/python_SDLog.cc	(revision 819)
+++ tags/asap1/src/python_SDLog.cc	(revision 819)
@@ -0,0 +1,51 @@
+//#---------------------------------------------------------------------------
+//# python_SDLog.cc: python exposure of c++ SDLog 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 "SDLog.h"
+
+using namespace boost::python;
+
+namespace asap {
+  namespace python {
+    void python_SDLog() {
+      class_<SDLog>("Log")
+        .def( init <> () )
+        //.def( init <bool> () )
+	.def("push", &SDLog::pushLog)
+	.def("pop", &SDLog::popLog)
+	.def("disable", &SDLog::disableLog)
+	.def("enable", &SDLog::enableLog)
+      ;
+    };
+
+  } //namespace python
+} // namespace asap
Index: tags/asap1/src/python_SDMath.cc
===================================================================
--- tags/asap1/src/python_SDMath.cc	(revision 819)
+++ tags/asap1/src/python_SDMath.cc	(revision 819)
@@ -0,0 +1,114 @@
+//#---------------------------------------------------------------------------
+//# python_SDMath.cc: python exposure of c++ SDMath 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 <vector>
+
+#include <casa/aips.h>
+#include <casa/Containers/Block.h>
+#include <casa/Utilities/CountedPtr.cc>
+
+#include "SDMathWrapper.h"
+#include "SDMath.h"
+
+using namespace casa;
+using namespace boost::python;
+
+namespace asap {
+  namespace SDMathWrapper {
+    SDMemTableWrapper SDMathWrapper::average(boost::python::tuple tp,
+                                             const std::vector<bool>& mask,
+                                             bool scanAv,
+                                             const std::string& weightStr) {
+      int n;
+      n = extract<int>(tp.attr("__len__")());
+      std::vector<CountedPtr<asap::SDMemTable> > b(n);
+      //Block<CountedPtr<asap::SDMemTable> > b(n);
+      for (int i=0;i< n;++i) {
+        SDMemTableWrapper sdmw =
+          extract<SDMemTableWrapper>( tp.attr("__getitem__")(i) );
+        b[i] = sdmw.getCP();
+      }
+      Vector<Bool> msk(mask);
+
+      return SDMemTableWrapper(sdmath.average(b, msk, Bool(scanAv), weightStr));
+    };
+  } // namespace SDMathWrapper
+
+  namespace python {
+    void python_SDMath() {
+      def("b_operate", &SDMathWrapper::binaryOperate);
+
+      def("quotient", &SDMathWrapper::quotient);
+
+      def("scale", &SDMathWrapper::scale);
+      def("scale_insitu", &SDMathWrapper::scaleInSitu);
+
+      def("add", &SDMathWrapper::add);
+      def("add_insitu", &SDMathWrapper::addInSitu);
+
+      def("smooth", &SDMathWrapper::smooth);
+      def("smooth_insitu", &SDMathWrapper::smoothInSitu);
+
+      def("convertflux", &SDMathWrapper::convertFlux);
+      def("convertflux_insitu", &SDMathWrapper::convertFluxInSitu);
+
+      def("gainel", &SDMathWrapper::gainElevation);
+      def("gainel_insitu", &SDMathWrapper::gainElevationInSitu);
+
+      def("freq_align", &SDMathWrapper::frequencyAlignment);
+      def("freq_align_insitu", &SDMathWrapper::frequencyAlignmentInSitu);
+
+      def("opacity", &SDMathWrapper::opacity);
+      def("opacity_insitu", &SDMathWrapper::opacityInSitu);
+
+      def("average", &SDMathWrapper::average);
+
+      def("averagepol", &SDMathWrapper::averagePol);
+      def("averagepol_insitu", &SDMathWrapper::averagePolInSitu);
+
+      def("bin", &SDMathWrapper::bin);
+      def("bin_insitu", &SDMathWrapper::binInSitu);
+
+      def("resample", &SDMathWrapper::resample);
+      def("resample_insitu", &SDMathWrapper::resampleInSitu);
+
+      def("stats", &SDMathWrapper::statistic);
+
+      def("_rotate_xyphase", &SDMathWrapper::rotateXYPhaseInSitu);
+      def("_rotate_linpolphase", &SDMathWrapper::rotateLinPolPhaseInSitu);
+
+      def("_frequency_switch", &SDMathWrapper::frequencySwitch);
+      def("_frequency_switch_insitu", &SDMathWrapper::frequencySwitchInSitu);
+    };
+
+  } // python
+} // asap
+
Index: tags/asap1/src/python_SDMemTable.cc
===================================================================
--- tags/asap1/src/python_SDMemTable.cc	(revision 819)
+++ tags/asap1/src/python_SDMemTable.cc	(revision 819)
@@ -0,0 +1,121 @@
+//#---------------------------------------------------------------------------
+//# python_SDMemTable.cc: python exposure of c++ SDMemTable 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 "SDMemTableWrapper.h"
+
+using namespace boost::python;
+
+namespace asap {
+  namespace python {
+
+void python_SDMemTable() {
+  class_<SDMemTableWrapper>("sdtable")
+    //.def( init <> () )
+    .def( init < std::string > () )
+    .def( init < const SDMemTableWrapper& > () )
+    .def( init < const SDMemTableWrapper&, std::string > () )
+    .def("_copy", &SDMemTableWrapper::copy)
+    .def("getif", &SDMemTableWrapper::getIF)
+    .def("getbeam", &SDMemTableWrapper::getBeam)
+    .def("getpol", &SDMemTableWrapper::getPol)
+    .def("_lines", &SDMemTableWrapper::spectralLines)
+    .def("nif", &SDMemTableWrapper::nIF)
+    .def("nbeam", &SDMemTableWrapper::nBeam)
+    .def("npol", &SDMemTableWrapper::nPol)
+    .def("nchan", &SDMemTableWrapper::nChan)
+    .def("nscan", &SDMemTableWrapper::nScan)
+    .def("nrow", &SDMemTableWrapper::nRow)
+    .def("setif", &SDMemTableWrapper::setIF,
+         (boost::python::arg("whichIF")=0) )
+    .def("setbeam", &SDMemTableWrapper::setBeam)
+    .def("setpol", &SDMemTableWrapper::setPol)
+    .def("get_fluxunit", &SDMemTableWrapper::getFluxUnit)
+    .def("set_fluxunit", &SDMemTableWrapper::setFluxUnit)
+    .def("_setInstrument", &SDMemTableWrapper::setInstrument)
+    .def("_getscan", &SDMemTableWrapper::getScan)
+    .def("_getsource", &SDMemTableWrapper::getSource)
+    .def("_getspectrum", &SDMemTableWrapper::getSpectrum,
+         (boost::python::arg("whichRow")=0))
+    .def("nstokes", &SDMemTableWrapper::nStokes)
+    .def("_getstokesspectrum", &SDMemTableWrapper::getStokesSpectrum,
+         (boost::python::arg("whichRow")=0),
+         (boost::python::arg("linpol")=false) )
+    .def("_stokestopolspectrum", &SDMemTableWrapper::stokesToPolSpectrum,
+         (boost::python::arg("whichRow")=0),
+         (boost::python::arg("linear")=false),
+         (boost::python::arg("thepol")=-1) )
+    .def("_getpolarizationlabel", &SDMemTableWrapper::getPolarizationLabel,
+         (boost::python::arg("linear")=false),
+         (boost::python::arg("stokes")=false),
+         (boost::python::arg("linpol")=false) )
+//         (boost::python::arg("thepol")=0) )        // Boost fails with 4 arguments
+    .def("_setspectrum",&SDMemTableWrapper::setSpectrum,
+         (boost::python::arg("whichRow")=0) )
+    .def("_getabcissa", &SDMemTableWrapper::getAbcissa,
+         (boost::python::arg("whichRow")=0) )
+    .def("_getabcissalabel", &SDMemTableWrapper::getAbcissaString,
+         (boost::python::arg("whichRow")=0) )
+    .def("_getmask", &SDMemTableWrapper::getMask,
+         (boost::python::arg("whichRow")=0) )
+    .def("_gettsys", &SDMemTableWrapper::getTsys)
+    .def("_getsourcename", &SDMemTableWrapper::getSourceName,
+         (boost::python::arg("whichRow")=0) )
+    .def("_getelevation", &SDMemTableWrapper::getElevation,
+         (boost::python::arg("whichRow")=0) )
+    .def("_getazimuth", &SDMemTableWrapper::getAzimuth,
+         (boost::python::arg("whichRow")=0) )
+    .def("_getparangle", &SDMemTableWrapper::getParAngle,
+         (boost::python::arg("whichRow")=0) )
+    .def("_gettime", &SDMemTableWrapper::getTime,
+         (boost::python::arg("whichRow")=0) )
+    .def("_flag", &SDMemTableWrapper::flag,
+         (boost::python::arg("whichRow")=-1) )
+    .def("_save",  &SDMemTableWrapper::makePersistent)
+    .def("_summary",  &SDMemTableWrapper::summary,
+         (boost::python::arg("verbose")=true) )
+    .def("_getrestfreqs",  &SDMemTableWrapper::getRestFreqs)
+    .def("_setrestfreqs",  &SDMemTableWrapper::setRestFreqs)
+    .def("_setcoordinfo", &SDMemTableWrapper::setCoordInfo)
+    .def("_getcoordinfo", &SDMemTableWrapper::getCoordInfo)
+    .def("_gethistory", &SDMemTableWrapper::getHistory)
+    .def("_addhistory", &SDMemTableWrapper::addHistory)
+    .def("_addfit", &SDMemTableWrapper::addFit)
+    .def("_getfit", &SDMemTableWrapper::getSDFitTable)
+    .def("_recalc_azel", &SDMemTableWrapper::calculateAZEL)
+  ;
+};
+
+  } // python
+} // asap
Index: tags/asap1/src/python_SDReader.cc
===================================================================
--- tags/asap1/src/python_SDReader.cc	(revision 819)
+++ tags/asap1/src/python_SDReader.cc	(revision 819)
@@ -0,0 +1,53 @@
+//#---------------------------------------------------------------------------
+//# python_SDReader.cc: python exposure of c++ SDReader 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 "SDReaderWrapper.h"
+
+using namespace boost::python;
+
+namespace asap {
+  namespace python {
+
+    void python_SDReader() {
+      class_<SDReaderWrapper>("sdreader")
+        .def( init < std::string, int, int > () )
+        .def("_open", &SDReaderWrapper::open)
+        .def("_read", &SDReaderWrapper::read)
+        .def("_reset", &SDReaderWrapper::reset)
+        .def("_getdata", &SDReaderWrapper::getSDMemTable)
+        .def("_header",  &SDReaderWrapper::pseudoHeader)
+      ;
+    };
+
+  } //namespace python
+} // namespace asap
+
Index: tags/asap1/src/python_SDWriter.cc
===================================================================
--- tags/asap1/src/python_SDWriter.cc	(revision 819)
+++ tags/asap1/src/python_SDWriter.cc	(revision 819)
@@ -0,0 +1,51 @@
+//#---------------------------------------------------------------------------
+//# python_SDWriter.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 "SDWriterWrapper.h"
+
+using namespace boost::python;
+
+namespace asap {
+  namespace python  {
+
+void python_SDWriter() {
+  class_<SDWriterWrapper>("sdwriter")
+    .def(init <> ())
+    .def(init <std::string> ())
+    .def("setformat", &SDWriterWrapper::setFormat,
+        (boost::python::arg("format")="SDFITS"))
+    .def("write",     &SDWriterWrapper::write);
+  ;
+};
+
+  } // namespace python
+} // namespace asap
Index: tags/asap1/test/file-io.py
===================================================================
--- tags/asap1/test/file-io.py	(revision 819)
+++ tags/asap1/test/file-io.py	(revision 819)
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+from asap import *
+
+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.fits',format='FITS',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/asap1/test/mopra.py
===================================================================
--- tags/asap1/test/mopra.py	(revision 819)
+++ tags/asap1/test/mopra.py	(revision 819)
@@ -0,0 +1,27 @@
+#!/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()
+q2 = quotient(s.get_scan("*_S"),s.get_scan("*_R"))
+del q2,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 q.summary()
+print "Fitter test..."
+f = fitter()
+f.set_scan(q)
+q.set_cursor(IF=0)
+f.set_function(gauss=2) # fit two gaussians
+f.fit()
+fp = f.get_parameters()
+print "Mopra Test successful"
Index: tags/asap1/test/parkes-pol.py
===================================================================
--- tags/asap1/test/parkes-pol.py	(revision 819)
+++ tags/asap1/test/parkes-pol.py	(revision 819)
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+from asap import *
+
+rcParams['verbose'] = 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')
+
+# Make a copy for the 1667 transition
+data_1667 = data_1665.copy()
+data_1667.set_restfreqs(lines = ['OH1667'])
+data_1667.set_unit('km/s')
+data_1667.set_freqframe('LSRK')
+
+
+# Look at the first scan
+d1_5 = data_1665.get_scan(0)
+d1_7 = data_1667.get_scan(0)
+
+# Baseline
+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)
+
+# Plot the results
+plotter.plot(d1_5,d1_7)
+plotter.set_mode('p','s')
+plotter.set_layout(2,1)
+plotter.set_range(-30,0)
+plotter.set_cursor(pol=['I','Q','U','V'])
+plotter.set_cursor(pol=['I','Plinear'])
+plotter.set_cursor(pol=['RR','LL'])
+plotter.save('output/parkes_rrll.png')
+
+print "Parkes-Pol Test successful"
Index: tags/asap1/test/tid.py
===================================================================
--- tags/asap1/test/tid.py	(revision 819)
+++ tags/asap1/test/tid.py	(revision 819)
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+from asap import *
+
+rcParams['verbose'] = 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')
+s = data.get_scan('*[^we]')
+r = data.get_scan('*[we]')
+q = quotient(s,r)
+
+# 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(perif=True)
+
+# Recalculate the az/el
+
+q.recalc_azel()
+
+# Correct for gain curve and opacity
+q.gain_el()
+q.opacity(0.075)
+
+# Average in time
+av = average_time(q)
+
+# Baseline
+msk=av.create_mask([-70,-50],[40,60])
+av.poly_baseline(msk,1)
+
+plotter.set_mode('i')
+plotter.plot(av)
+plotter.save('output/tid.png')
+
+# 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/asap1/web/commands.html
===================================================================
--- tags/asap1/web/commands.html	(revision 819)
+++ tags/asap1/web/commands.html	(revision 819)
@@ -0,0 +1,205 @@
+<!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
+----> 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)
+            summary         - print info about the scantable contents
+            set_cursor      - set a specific Beam/IF/Pol 'cursor' for
+                              further use
+            get_cursor      - print out the current cursor position
+            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 currnt 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_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
+            lines           - print list of known spectral lines
+            flag_spectrum   - flag a whole Beam/IF/Pol
+            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 integrations 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.
+            auto_quotient   - return the on/off quotient with
+                              automatic detection of the on/off scans
+                              (matched pairs and 1 off - n on)
+            quotient        - return the on/off quotient
+            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 paramaters 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 (list of) 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_cursor      - 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
+            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
+        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/asap1/web/index.html
===================================================================
--- tags/asap1/web/index.html	(revision 819)
+++ tags/asap1/web/index.html	(revision 819)
@@ -0,0 +1,125 @@
+<!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 1.2.1 was released on December, 6<sup>th</sup> 2005. 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 v1.2.1 (2005/12/06) - the ATNF Spectral Analysis Package
+
+Please report any bugs to:
+asap@atnf.csiro.au
+
+[IMPORTANT: ASAP is 0-based]
+Type commands() to get a list of all available ASAP commands.
+
+ASAP>
+</pre>
+
+
+<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>
+
+<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-requests@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-requests@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>Current stable release source code snapshot <a
+      href="ftp://ftp.atnf.csiro.au/pub/software/asap/asap_src.tar.bz2">asap source</a> </li>
+  <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>
+</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/asap1/web/intro.html
===================================================================
--- tags/asap1/web/intro.html	(revision 819)
+++ tags/asap1/web/intro.html	(revision 819)
@@ -0,0 +1,52 @@
+<!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.
+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/asap1/web/osx.html
===================================================================
--- tags/asap1/web/osx.html	(revision 819)
+++ tags/asap1/web/osx.html	(revision 819)
@@ -0,0 +1,109 @@
+<!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. Obviously, steps specific for
+10.4 have to be omitted.
+<h2>Getting stuff</h2>
+<a href="http://pythonmac.org/packages">pythonmac.org</a> has
+pre-packaged python modules.
+<br>
+From the section <b>Mac OS X 10.4 (stock Python 2.3.5) </b>
+<br>get
+<ul>
+  <li>
+<a
+      href="http://pythonmac.org/packages/TigerPython23Compat.pkg.zip">TigerPython23Compat</a> </li>
+</ul>
+From the section <b>Mac OS X 10.3 (stock Python 2.3.0) </b>
+<br>get
+<ul>
+  <li>
+<a
+      href="http://pythonmac.org/packages/Numeric-23.7-py2.3-macosx10.3.zip">Numeric</a> </li>
+  <li>
+<a
+      href="http://pythonmac.org/packages/numarray-1.1.1-py2.3-macosx10.3.zip">numarray</a> </li>
+<a
+      href="http://pythonmac.org/packages/matplotlib-0.82-py2.3-macosx10.3.zip">matplotlib</a> </li>
+</ul>
+<br>
+Next get <a href="http://ipython.scipy.org/dist">ipython</a> and run:<br>
+<pre>
+tar zxvf ipython-0.6.15.tar.gz
+sudo python setup.py install
+sudo ln -s ../../System/Library/Frameworks/Python.framework/Versions/2.3/bin/ipython /usr/bin/ipython
+</pre>
+
+
+<h2>Tweaking it</h2>
+Run the following in a terminal:<br>
+OSX comes with your own pimp pre-installed, so better use him!<br>
+<pre>
+sudo python `python -c "import pimp; print pimp.__file__"` -i readline
+</pre>
+
+Then as 'root' or 'sudo' edit<br>
+<b>/System/Library/Frameworks/Python.framework/Versions/2.3/share/matplotlib/.matplotlibrc</b><br>
+In this file replace<br>
+<b> backend    : GTKAgg</b><br>
+with<br>
+<b> backend    : TkAgg</b><br>
+<p>
+Now we have to patch matplotlib:
+Get <a href="./figure.py">this</a> and run:<br>
+<pre>
+sudo cp -f figure.py /Library/Python/2.3/matplotlib/
+</pre>
+
+<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/asap1/web/screenshots.html
===================================================================
--- tags/asap1/web/screenshots.html	(revision 819)
+++ tags/asap1/web/screenshots.html	(revision 819)
@@ -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/asap1/web/tutorials/index.html
===================================================================
--- tags/asap1/web/tutorials/index.html	(revision 819)
+++ tags/asap1/web/tutorials/index.html	(revision 819)
@@ -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/asap1/web/tutorials/tutorial.html
===================================================================
--- tags/asap1/web/tutorials/tutorial.html	(revision 819)
+++ tags/asap1/web/tutorials/tutorial.html	(revision 819)
@@ -0,0 +1,145 @@
+<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 th 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. We are taking all scans ending in "_S" to be "on" source and all "_R" to be "off".
+<div class=code><pre>
+q = quotient(s.get_scan("*_S"),s.get_scan("*_R"))
+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()
+f.set_scan(q)
+</pre></div>
+We start with the first spectrum (IF=0) bys setting the cursor. We want to fits to gaussian components.
+<div class=code><pre>
+q.set_cursor(IF=0)
+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>
+q.set_cursor(IF=1)
+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/asap1/web/tutorials/tutorial2.html
===================================================================
--- tags/asap1/web/tutorials/tutorial2.html	(revision 819)
+++ tags/asap1/web/tutorials/tutorial2.html	(revision 819)
@@ -0,0 +1,115 @@
+<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
+   on = scan.get_source("*_S")        
+   off = scan.get_source("*_R")
+   quotients.append(quotient(on,off)) # 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>
