\chapter{Developer Manual (04-apr-2025)}
\label{sec:developer}

Please check directly the \class{} sources to get the most up-to-date
information.

\section{Internal \class{} Format}

The \class{} Data Format is built upon the Classic Data
Container. Please refer to the dedicated documentation for details.

\subsection{Contents of one observation}

Data, \ie{} observational parameters as well as spectra, is organized in
the following way:
\begin{itemize}
\item One observation is self-contained. All the information needed to
  reduce it is recorded on the same few disk blocks. It may be one spectrum
  or one continuum drift.
\item Each observation is divided in several sections, containing header
  parameters or data:
  \begin{itemize}
  \item General information (date, times, local coordinates, sequence
    number, ...)
  \item Positional information (source, name, astronomical
    coordinates, equinox, offsets, ...)
  \item Spectral information (number of spectra, line names ans
    frequencies, resolutions, number of channels, ...)
  \item Data
  \item ...
\end{itemize}
\end{itemize}

In the Classic nomenclature, one observation is saved into one
\emph{entry} in a Classic file. Each entry is composed of an entry
descriptor + the observation itself (header+intensity array). Please
refer to the section \emph{The File Entries} in the Classic
documentation for details about the way an entry is written in the
file.

\subsection{File organization}

\class{} files strictly respect the Classic Data Container
specifications (see the dedicated documentation for details). In
particular, the file identification codes (first 4 bytes) are
described in this documentation. \class{} files use the \emph{kind} 1
in the File Descriptor.

\subsection{File index}

The Entry Indexes, gathered into Extension Indexes, are ruled by the
Classic Data Container specifications. According to these, the index
contents are let free to Class. They are described below for files
version 1 and 2.

\begin{table}[htb]
\begin{center}
\caption{Index version 1 (found in files version 1). Note that index
  length is 32 words (so that 4 of them fit in a 128 words record),
  even if 10 words are left unused. The subscan number was originally
  absent from the old version of the data format (see related memo for
  details); it is read as 0 from such files.}
\vspace{1ex}
\begin{tabular}{c|cl|c|l}
Position & Parameter     & Fortran Kind & Unit     & Purpose \\
\hline
  1      & {\tt bloc}    & Integer*4    & Record   & Record where observation is to be found \\
  2      & {\tt num}     & Integer*4    &          & Observation number \\
  3      & {\tt ver}     & Integer*4    &          & Observation version \\
  4-6    & {\tt csour}   & Character*12 &          & Source name \\
  7-9    & {\tt cline}   & Character*12 &          & Line name \\
  10-12  & {\tt ctele}   & Character*12 &          & Telescope name \\
  13     & {\tt dobs}    & Integer*4    & GAG date & Observation date \\
  14     & {\tt dred}    & Integer*4    & GAG date & Reduction date \\
  15     & {\tt off1}    & Real*4       & Radian   & Lambda offset \\
  16     & {\tt off2}    & Real*4       & Radian   & Beta offset \\
  17     & {\tt type}    & Integer*4    & Code     & Coordinate system \\
  18     & {\tt kind}    & Integer*4    & Code     & Kind of data \\
  19     & {\tt qual}    & Integer*4    & Code     & Quality of data \\
  20     & {\tt scan}    & Integer*4    &          & Scan number \\
  21     & {\tt posa}    & Real*4       & Radian   & Position angle (drifts) \\
  22     & {\tt subscan} & Integer*4    &          & Subscan number (absent from old data format) \\
  23-32  & (unused)      &              &          & Padding to 32 words
\end{tabular}
\end{center}
\end{table}

\begin{table}[htb]
\begin{center}
\caption{Index version 2 (found in files version 2)}
\vspace{1ex}
\begin{tabular}{c|cl|c|l}
Position & Parameter     & Fortran Kind & Unit     & Purpose \\
\hline
  1-2    & {\tt bloc}    & Integer*8    & Record   & Record where observation is to be found \\
  3      & {\tt word}    & Integer*4    & Word     & Word where the obs. starts in this record \\
  4-5    & {\tt num}     & Integer*8    &          & Observation number \\
  6      & {\tt ver}     & Integer*4    &          & Observation version \\
  7-9    & {\tt csour}   & Character*12 &          & Source name \\
  10-12  & {\tt cline}   & Character*12 &          & Line name \\
  13-15  & {\tt ctele}   & Character*12 &          & Telescope name \\
  16     & {\tt dobs}    & Integer*4    & GAG date & Observation date \\
  17     & {\tt dred}    & Integer*4    & GAG date & Reduction date \\
  18     & {\tt off1}    & Real*4       & Radian   & Lambda offset \\
  19     & {\tt off2}    & Real*4       & Radian   & Beta offset \\
  20     & {\tt type}    & Integer*4    & Code     & Coordinate system \\
  21     & {\tt kind}    & Integer*4    & Code     & Kind of data \\
  22     & {\tt qual}    & Integer*4    & Code     & Quality of data \\
  23     & {\tt posa}    & Real*4       & Radian   & Position angle (drifts) \\
  24-25  & {\tt scan}    & Integer*8    &          & Scan number \\
  26     & {\tt subscan} & Integer*4    &          & Subscan number
\end{tabular}
\end{center}
\end{table}

\newpage
\section{The Header and Data Sections}

Here we describe the contents of the main header sections (except from
the storage declarations in the \class{} program itself). Note that, in the
data file, the actual length of some of the sections is variable, \eg{} the
length of the switching information section depends on the number of
different phases in the switching procedure.

\subsection{General Parameters}

The General section in memory has the particularity to concatenate
information written in the index on disk and written in the General
section on disk.

Observation version 2 since 10-mar-2023 (added parallactic angle):
\begin{verbatim}
  ! GENERAL: General parameters, always present.
  integer(kind=4), parameter :: class_sec_gen_id=-2
  integer(kind=4), parameter :: class_sec_gen_len=11
  type general
     sequence
     ! Written in the index on disk
     integer(kind=4) :: num     ! [         ] Observation number
     integer(kind=4) :: ver     ! [         ] Version number
     integer(kind=4) :: teles(3)! [         ] Telescope name
     integer(kind=4) :: dobs    ! [MJD-60549] Date of observation
     integer(kind=4) :: dred    ! [MJD-60549] Date of reduction
     integer(kind=4) :: kind    ! [     code] Type of data
     integer(kind=4) :: qual    ! [     code] Quality of data
     integer(kind=4) :: scan    ! [         ] Scan number
     integer(kind=4) :: subscan ! [         ] Subscan number
     ! Written in the section on disk
     real(kind=8)    :: ut      ! [  rad] UT of observation
     real(kind=8)    :: st      ! [  rad] LST of observation
     real(kind=4)    :: az      ! [  rad] Azimuth
     real(kind=4)    :: el      ! [  rad] Elevation
     real(kind=4)    :: tau     ! [neper] Opacity
     real(kind=4)    :: tsys    ! [    K] System temperature
     real(kind=4)    :: time    ! [    s] Integration time
     real(kind=8)    :: parang  ! [  rad] Parallactic angle
     integer(kind=4) :: xunit   ! [ code] X unit (if X coordinates section is present)
  end type general
\end{verbatim}

Observation version 1/2 (obsolete for writing):
\begin{verbatim}
  ! GENERAL: General parameters, always present.
  integer(kind=4), parameter :: class_sec_gen_id=-2
  integer(kind=4), parameter :: class_sec_gen_len=9
  type general
     sequence
     ! Written in the index on disk
     integer(kind=4) :: num     ! [         ] Observation number
     integer(kind=4) :: ver     ! [         ] Version number
     integer(kind=4) :: teles(3)! [         ] Telescope name
     integer(kind=4) :: dobs    ! [MJD-60549] Date of observation
     integer(kind=4) :: dred    ! [MJD-60549] Date of reduction
     integer(kind=4) :: kind    ! [     code] Type of data
     integer(kind=4) :: qual    ! [     code] Quality of data
     integer(kind=4) :: scan    ! [         ] Scan number
     integer(kind=4) :: subscan ! [         ] Subscan number
     ! Written in the section on disk
     real(kind=8)    :: ut      ! [  rad] UT of observation
     real(kind=8)    :: st      ! [  rad] LST of observation
     real(kind=4)    :: az      ! [  rad] Azimuth
     real(kind=4)    :: el      ! [  rad] Elevation
     real(kind=4)    :: tau     ! [neper] Opacity
     real(kind=4)    :: tsys    ! [    K] System temperature
     real(kind=4)    :: time    ! [    s] Integration time
     integer(kind=4) :: xunit   ! [ code] X unit (if X coordinates section is present)
  end type general
\end{verbatim}

\subsection{Position information}

Observation version 2 since 26-mar-2015:
\begin{verbatim}
  ! POSITION: Position information.
  integer(kind=4), parameter :: class_sec_pos_id=-3
  integer(kind=4), parameter :: class_sec_pos_len=14
  type position
     sequence
     integer(kind=4) :: sourc(3) ! [    ] Source name
     integer(kind=4) :: system   ! [code] Coordinate system
     real(kind=4)    :: equinox  ! [year] Equinox of coordinates
     integer(kind=4) :: proj     ! [code] Projection system
     real(kind=8)    :: lam      ! [ rad] Lambda of projection center
     real(kind=8)    :: bet      ! [ rad] Beta of projection center
     real(kind=8)    :: projang  ! [ rad] Projection angle
     real(kind=4)    :: lamof    ! [ rad] Offset in Lambda
     real(kind=4)    :: betof    ! [ rad] Offset in Beta
  end type position
\end{verbatim}

Observation version 1 (obsolete for writing):
\begin{verbatim}
  ! POSITION: Position information.
  integer(kind=4), parameter :: class_sec_pos_id=-3
  integer(kind=4), parameter :: class_sec_pos_len=17
  type position
     sequence
     integer(kind=4) :: sourc(3) ! [    ] Source name
     real(kind=4)    :: equinox  ! [year] Equinox of coordinates
     real(kind=8)    :: lam      ! [ rad] Lambda
     real(kind=8)    :: bet      ! [ rad] Beta
     real(kind=4)    :: lamof    ! [ rad] Offset in Lambda
     real(kind=4)    :: betof    ! [ rad] Offset in Beta
     integer(kind=4) :: proj     ! [code] Projection system
     real(kind=8)    :: sl0p     ! [ rad] lambda of descriptive system
     real(kind=8)    :: sb0p     ! [ rad] beta   of descriptive system
     real(kind=8)    :: sk0p     ! [ rad] angle  of descriptive system
  end type position
\end{verbatim}

\subsection{Spectroscopic information (for spectra)}

Observation version 1.3 (Doppler correction added, SkyFr and Vteles
removed):

\begin{verbatim}
  ! SPECTRO: Spectroscopic information (for spectra).
  integer(kind=4), parameter :: class_sec_spe_id=-4
  integer(kind=4), parameter :: class_sec_spe_len=17
  type spectro
     sequence
     integer(kind=4) :: line(3)  ! [    ] Line name
     real(kind=8)    :: restf    ! [ MHz] Rest frequency
     integer(kind=4) :: nchan    ! [    ] Number of channels
     real(kind=4)    :: rchan    ! [    ] Reference channel
     real(kind=4)    :: fres     ! [ MHz] Frequency resolution
     real(kind=4)    :: foff     ! [ MHz] Frequency offset
     real(kind=4)    :: vres     ! [km/s] Velocity resolution
     real(kind=4)    :: voff     ! [km/s] Velocity at reference channel
     real(kind=4)    :: bad      ! [    ] Blanking value
     real(kind=8)    :: image    ! [ MHz] Image frequency
     integer(kind=4) :: vtype    ! [code] Type of velocity
     real(kind=8)    :: doppler  ! [km/s] Doppler correction -V/c (CLASS convention)
  end type spectro
\end{verbatim}

Observation version 1.2 (SkyFr and Vteles added), obsolete for
writing:

\begin{verbatim}
  ! SPECTRO: Spectroscopic information (for spectra).
  integer(kind=4), parameter :: class_sec_spe_id=-4
  integer(kind=4), parameter :: class_sec_spe_len=18
  type spectro
     sequence
     integer(kind=4) :: line(3) ! [    ] Line name
     real(kind=8)    :: restf   ! [ MHz] Rest frequency
     integer(kind=4) :: nchan   ! [    ] Number of channels
     real(kind=4)    :: rchan   ! [    ] Reference channels
     real(kind=4)    :: fres    ! [ MHz] Frequency resolution
     real(kind=4)    :: foff    ! [ MHz] Frequency offset
     real(kind=4)    :: vres    ! [km/s] Velocity resolution
     real(kind=4)    :: voff    ! [km/s] Velocity at reference channel
     real(kind=4)    :: bad     ! [    ] Blanking value
     real(kind=8)    :: image   ! [ MHz] Image frequency
     integer(kind=4) :: vtype   ! [code] Type of velocity
     real(kind=8)    :: skyfr   ! [ MHz]
     real(kind=4)    :: vteles  ! [km/s]
  end type spectro
\end{verbatim}

Observation version 1.1, obsolete for writing:

\begin{verbatim}
  ! SPECTRO: Spectroscopic information (for spectra).
  integer(kind=4), parameter :: class_sec_spe_id=-4
  integer(kind=4), parameter :: class_sec_spe_len=15
  type spectro
     sequence
     integer(kind=4) :: line(3) ! [    ] Line name
     real(kind=8)    :: restf   ! [ MHz] Rest frequency
     integer(kind=4) :: nchan   ! [    ] Number of channels
     real(kind=4)    :: rchan   ! [    ] Reference channels
     real(kind=4)    :: fres    ! [ MHz] Frequency resolution
     real(kind=4)    :: foff    ! [ MHz] Frequency offset
     real(kind=4)    :: vres    ! [km/s] Velocity resolution
     real(kind=4)    :: voff    ! [km/s] Velocity at reference channel
     real(kind=4)    :: bad     ! [    ] Blanking value
     real(kind=8)    :: image   ! [ MHz] Image frequency
     integer(kind=4) :: vtype   ! [code] Type of velocity
  end type spectro
\end{verbatim}

\subsection{Baseline information (for spectra or drifts)}

\begin{verbatim}
  ! BASE: Baseline information (for spectra of drifts).
  integer(kind=4), parameter :: class_sec_bas_id=-5
  integer(kind=4), parameter :: mwind=100
  integer(kind=4), parameter :: class_sec_bas_len=8+2*mwind
  type base
     sequence
     integer(kind=4) :: deg         ! [         ] Degree of last baseline
     real(kind=4)    :: sigfi       ! [Int. unit] Sigma
     real(kind=4)    :: aire        ! [Int. unit] Area under windows
     integer(kind=4) :: nwind       ! [         ] Number of line windows
     real(kind=4)    :: w1(mwind)   ! [     km/s] Lower limits of windows
     real(kind=4)    :: w2(mwind)   ! [     km/s] Upper limits of windows
     real(kind=4)    :: sinus(3)    ! [         ] Sinus baseline results
  end type base
\end{verbatim}

\subsection{Scan numbers of initial observations}

\begin{verbatim}
  ! HISTORY: Scan numbers of initial observations.
  integer(kind=4), parameter :: class_sec_his_id=-6
  integer(kind=4), parameter :: mseq=100
  integer(kind=4), parameter :: class_sec_his_len=2*mseq+1
  type history
     sequence
     integer(kind=4) :: nseq        ! Number of sequences
     integer(kind=4) :: start(mseq) ! Start can number of seq.
     integer(kind=4) :: end(mseq)   ! End scan number of seq.
  end type history
\end{verbatim}

\subsection{Default plotting limits}

\begin{verbatim}
  ! PLOT: Default plotting limits.
  integer(kind=4), parameter :: class_sec_plo_id=-7
  integer(kind=4), parameter :: class_sec_plo_len=4
  type plot
     sequence
     real(kind=4)    :: amin    ! [Int. unit] Min Y value plotted
     real(kind=4)    :: amax    ! [Int. unit] Max Y value plotted
     real(kind=4)    :: vmin    ! [     km/s] Min X value plotted
     real(kind=4)    :: vmax    ! [     km/s] Max X value plotted
  end type plot
\end{verbatim}

\subsection{Switching information (for spectra)}

\begin{verbatim}
  ! SWITCH: Switching information (for spectra).
  integer(kind=4), parameter :: class_sec_swi_id=-8
  integer(kind=4), parameter :: mxphas=8
  integer(kind=4), parameter :: class_sec_swi_len=2+6*mxphas
  type class_switch_t
     sequence
     integer(kind=4) :: nphas           ! [    ] Number of phases
     real(kind=8)    :: decal(mxphas)   ! [ MHz] Frequency offsets
     real(kind=4)    :: duree(mxphas)   ! [   s] Time per phase
     real(kind=4)    :: poids(mxphas)   ! [    ] Weight of each phase
     integer(kind=4) :: swmod           ! [code] Switching mode (frequency, position...)
     real(kind=4)    :: ldecal(mxphas)  ! [ rad] Lambda offsets
     real(kind=4)    :: bdecal(mxphas)  ! [ rad] Beta offsets of each phase
  end type class_switch_t
\end{verbatim}

\subsection{Calibration parameters}

\begin{verbatim}
  ! CALIBRATION: Calibration parameters.
  integer(kind=4), parameter :: class_sec_cal_id=-14
  integer(kind=4), parameter :: class_sec_cal_len=25
  type calib
     sequence
     real(kind=4)    :: beeff    ! [     ] Beam efficiency
     real(kind=4)    :: foeff    ! [     ] Forward efficiency
     real(kind=4)    :: gaini    ! [     ] Image/Signal gain ratio
     real(kind=4)    :: h2omm    ! [   mm] Water vapor content
     real(kind=4)    :: pamb     ! [  hPa] Ambient pressure
     real(kind=4)    :: tamb     ! [    K] Ambient temperature
     real(kind=4)    :: tatms    ! [    K] Atmosphere temp. in signal band
     real(kind=4)    :: tchop    ! [    K] Chopper temperature
     real(kind=4)    :: tcold    ! [    K] Cold load temperature
     real(kind=4)    :: taus     ! [neper] Opacity in signal band
     real(kind=4)    :: taui     ! [neper] Opacity in image band
     real(kind=4)    :: tatmi    ! [    K] Atmosphere temp. in image band
     real(kind=4)    :: trec     ! [    K] Receiver temperature
     integer(kind=4) :: cmode    ! [ code] Calibration mode
     real(kind=4)    :: atfac    ! [     ] Applied calibration factor
     real(kind=4)    :: alti     ! [    m] Site elevation
     real(kind=4)    :: count(3) ! [count] Power of Atm., Chopp., Cold
     real(kind=4)    :: lcalof   ! [  rad] Longitude offset for sky measurement
     real(kind=4)    :: bcalof   ! [  rad] Latitude offset for sky measurement
     real(kind=8)    :: geolong  ! [  rad] Geographic longitude of observatory
     real(kind=8)    :: geolat   ! [  rad] Geographic latitude of observatory
  end type calib                  
\end{verbatim}

\subsection{For Skydips observations. No associated data.}

\begin{verbatim}
  ! SKYDIP: For Skydips observations. No associated data.
  integer(kind=4), parameter :: class_sec_sky_id=-16
  integer(kind=4), parameter :: msky=10
  integer(kind=4), parameter :: class_sec_sky_len=10+4*msky
  type skydip
     sequence
     integer(kind=4) :: line(3)     ! [   ] Line name
     real(kind=8)    :: restf       ! [MHz] Rest frequency
     real(kind=8)    :: image       ! [MHz] Image frequency
     integer(kind=4) :: nsky        ! [   ] Number of points on sky
     integer(kind=4) :: nchop       ! [   ]   -   -    -    - chopper
     integer(kind=4) :: ncold       ! [   ]   -   -    -    - cold load
     real(kind=4)    :: elev(msky)  ! [rad] Elevations
     real(kind=4)    :: emiss(msky) ! [  ?] Power on sky
     real(kind=4)    :: chopp(msky) ! [  ?] Power on chopper
     real(kind=4)    :: cold(msky)  ! [  ?] Power on cold load
  end type skydip
\end{verbatim}

\subsection{Gauss fit results (for spectra or drifts)}

Observation version 1 (dynamic size) since 22-feb-2021:\\
The section size is now dynamic and can offer space for less or more
than 5 lines, always with {\tt mgauslin} $\ge$ {\tt nline}. {\tt
  mgauslin}, and thus the array sizes ({\tt mgausfit}), is implicit
and can be guessed from the section length (by reverting the {\tt
  class\_sec\_gau\_len} formula below). Using {\tt mgauslin} $= 5$
ensures backward compatibility with older \class{} versions. Other
values are read incorrectly with such versions.\\

Observation version 1 (static size):
\begin{verbatim}
  ! GAUSS: Gauss fit results (for spectra or drifts).
  integer(kind=4), parameter :: class_sec_gau_id=-9
  integer(kind=4), parameter :: mgauslin=5
  integer(kind=4), parameter :: ngauspar=3  ! Number of parameters per line
  integer(kind=4), parameter :: mgausfit=ngauspar*mxgaus
  integer(kind=4), parameter :: class_sec_gau_len=3+2*mgausfit
  type gauss
     sequence
     integer(kind=4) :: nline           ! [            ] Number of components
     real(kind=4)    :: sigba           ! [   Int. unit] Sigma on base
     real(kind=4)    :: sigra           ! [   Int. unit] Sigma on line
     real(kind=4)    :: nfit(mgausfit)  ! [area,v0,fwhm] Fit results
     real(kind=4)    :: nerr(mgausfit)  ! [area,v0,fwhm] Errors
  end type gauss
\end{verbatim}

\subsection{"Stellar shell" profile fit results (for spectra)}

Observation version 1 (dynamic size) since 22-feb-2021:\\
The section size is now dynamic and can offer space for less or more
than 5 lines, always with {\tt mshelllin} $\ge$ {\tt nline}. {\tt
  mshelllin}, and thus the array sizes ({\tt mshellfit}), is implicit
and can be guessed from the section length (by reverting the {\tt
  class\_sec\_she\_len} formula below). Using {\tt mshelllin} $= 5$
ensures backward compatibility with older \class{} versions. Other
values are read incorrectly with such versions.\\

Observation version 1 (static size):
\begin{verbatim}
  ! SHELL: "Stellar shell" profile fit results (for spectra).
  integer(kind=4), parameter :: class_sec_she_id=-12
  integer(kind=4), parameter :: mshelllin=5
  integer(kind=4), parameter :: nshellpar=4  ! Number of parameters per line
  integer(kind=4), parameter :: mshellfit=nshellpar*mshelllin
  integer(kind=4), parameter :: class_sec_she_len=3+2*mshellfit
  type shell
     sequence
     integer(kind=4) :: nline            ! [] Number of components
     real(kind=4)    :: sigba            ! [] Sigma on base
     real(kind=4)    :: sigra            ! [] Sigma on line
     real(kind=4)    :: nfit(mshellfit)  ! [] Fit results
     real(kind=4)    :: nerr(mshellfit)  ! [] Errors
  end type shell
\end{verbatim}

\subsection{Hyperfine structure profile fit results (for spectra)}

Observation version 1 (dynamic size) since 22-feb-2021:\\
The section size is now dynamic and can offer space for less or more
than 3 lines, always with {\tt mhfslin} $\ge$ {\tt nline}. {\tt
  mhfslin}, and thus the array sizes ({\tt mhfsfit}), is implicit and
can be guessed from the section length (by reverting the {\tt
  class\_sec\_hfs\_len} formula below). Using {\tt mhfslin} $= 3$
ensures backward compatibility with older \class{} versions. Other
values are read incorrectly with such versions.\\

Observation version 1 (static size):
\begin{verbatim}
  ! HFS: "Hyperfine Structure" profile fit results (e.g. NH3, HCN, for spectra).
  integer(kind=4), parameter :: class_sec_hfs_id=-13
  integer(kind=4), parameter :: mhfslin=3
  integer(kind=4), parameter :: nhfspar=4  ! Number of parameters per line
  integer(kind=4), parameter :: mhfsfit=nhfspar*mhfslin
  integer(kind=4), parameter :: class_sec_hfs_len=3+2*mhfsfit
  type hfs
     sequence
     integer(kind=4) :: nline          ! Number of components
     real(kind=4)    :: sigba          ! Sigma on base
     real(kind=4)    :: sigra          ! Sigma on line
     real(kind=4)    :: nfit(mhfsfit)  ! Fit results
     real(kind=4)    :: nerr(mhfsfit)  ! Errors
  end type hfs
\end{verbatim}

\subsection{Hyperfine structure absorption profile fit results (for spectra)}

Observation version 1 (dynamic size) since 22-feb-2021:\\
The section size is now dynamic and can offer space for less or more
than 5 lines, always with {\tt mabslin} $\ge$ {\tt nline}. {\tt
  mabslin}, and thus the array sizes ({\tt mabsfit}), is implicit and
can be guessed from the section length (by reverting the {\tt
  class\_sec\_abs\_len} formula below). Using {\tt mabslin} $= 5$
ensures backward compatibility with older \class{} versions. Other
values are read incorrectly with such versions.\\

Observation version 1 (static size):
\begin{verbatim}
  ! ABS: "Hyperfine Structure" absorption profile fit results (e.g. NH3, HCN, for spectra).
  integer(kind=4), parameter :: class_sec_abs_id=-18   ! Absorption fit
  integer(kind=4), parameter :: mabslin=5
  integer(kind=4), parameter :: nabspar=3  ! Number of parameters per line
  integer(kind=4), parameter :: mabsfit=1+nabspar*mabslin  ! First is continuum
  integer(kind=4), parameter :: class_sec_abs_len=3+2*mabsfit
  type absorption
     sequence
     integer(kind=4) :: nline          ! Number of components
     real(kind=4)    :: sigba          ! Sigma on base
     real(kind=4)    :: sigra          ! Sigma on line
     real(kind=4)    :: nfit(mabsfit)  ! Fit results
     real(kind=4)    :: nerr(mabsfit)  ! Errors
  end type absorption
\end{verbatim}
 
\subsection{Continuum drift description (for drifts)}

\begin{verbatim}
  ! DRIFT: Continuum drift description (for drifts).
  integer(kind=4), parameter :: class_sec_dri_id=-10
  integer(kind=4), parameter :: class_sec_dri_len=16
  type drift
     sequence
     real(kind=8)    :: freq    ! [ MHz] Rest frequency
     real(kind=4)    :: width   ! [ MHz] Bandwidth
     integer(kind=4) :: npoin   ! [    ] Number of data points
     real(kind=4)    :: rpoin   ! [    ] Reference point
     real(kind=4)    :: tref    ! [   ?] Time at reference
     real(kind=4)    :: aref    ! [ rad] Angular offset at ref.
     real(kind=4)    :: apos    ! [ rad] Position angle of drift
     real(kind=4)    :: tres    ! [   ?] Time resolution
     real(kind=4)    :: ares    ! [ rad] Angular resolution
     real(kind=4)    :: bad     ! [    ] Blanking value
     integer(kind=4) :: ctype   ! [code] Type of offsets
     real(kind=8)    :: cimag   ! [ MHz] Image frequency
     real(kind=4)    :: colla   ! [   ?] Collimation error Az
     real(kind=4)    :: colle   ! [   ?] Collimation error El
  end type drift
\end{verbatim}

\subsection{Beam-switching parameters (for spectra or drifts)}

\begin{verbatim}
  ! BEAM: Beam-switching parameters (for spectra or drifts).
  integer(kind=4), parameter :: class_sec_bea_id=-11
  integer(kind=4), parameter :: class_sec_bea_len=5
  type beam
     sequence
     real(kind=4)    :: cazim   ! [ rad] Azimuth of observation
     real(kind=4)    :: celev   ! [ rad] Elevation of observation
     real(kind=4)    :: space   ! [ rad] Beam spacing
     real(kind=4)    :: bpos    ! [ rad] Position angle of beams
     integer(kind=4) :: btype   ! [code] System for angle
  end type beam
\end{verbatim}

\subsection{Double gaussian and baseline fit results (for drifts)}

\begin{verbatim}
  ! CONTINUUM: Double gaussian and baseline fit results (for drifts).
  integer(kind=4), parameter :: class_sec_poi_id=-15
  integer(kind=4), parameter :: mpoifit=8
  integer(kind=4), parameter :: class_sec_poi_len=3+2*mpoifit
  type pointing
     sequence 
     integer(kind=4) :: nline          ! Number of components
     real(kind=4)    :: sigba          ! Sigma on base
     real(kind=4)    :: sigra          ! Sigma on line
     real(kind=4)    :: nfit(mpoifit)  ! Fit results
     real(kind=4)    :: nerr(mpoifit)  ! Errors
  end type pointing
\end{verbatim}


\subsection{Herschel Space Observatory (HIFI)}

\begin{verbatim}
  integer(kind=4), parameter :: class_sec_her_id=-20
  integer(kind=4), parameter :: class_sec_her_len=96
  type class_herschel_t
    integer(kind=8)   :: obsid       !  1- 2  [----] Observation id                                                    
    character(len=8)  :: instrument  !  3- 4  [str ] Instrument name                                                   
    character(len=24) :: proposal    !  5-10  [str ] Proposal name                                                     
    character(len=68) :: aor         ! 11-27  [str ] Astronomical Observation Request                                  
    integer(kind=4)   :: operday     !    28  [day ] Operational day number                                            
    character(len=28) :: dateobs     ! 29-35  [str ] Start date (ISO date)                                             
    character(len=28) :: dateend     ! 36-42  [str ] End date (ISO date)                                               
    character(len=40) :: obsmode     ! 43-52  [str ] Observing mode                                                    
    real(kind=4)      :: vinfo       !    53  [km/s] Informative source velocity in LSR frame                          
    real(kind=4)      :: zinfo       !    54  [----] Informative target redshift                                       
    real(kind=8)      :: posangle    ! 55-56  [rad ] Spacecraft pointing position angle                                
    real(kind=8)      :: reflam      ! 57-58  [rad ] Sky reference (a.k.a. OFF), lambda coordinate                     
    real(kind=8)      :: refbet      ! 59-60  [rad ] Sky reference (a.k.a. OFF), beta coordinate                       
    real(kind=8)      :: hifavelam   ! 61-62  [rad ] ON average H and V coordinate (HIFI), lambda coordinate           
    real(kind=8)      :: hifavebet   ! 63-64  [rad ] ON average H and V coordinate (HIFI), beta coordinate             
    real(kind=4)      :: etamb       !    65  [----] Main beam efficiency used when applying doAntennaTemp             
    real(kind=4)      :: etal        !    66  [----] Forward efficiency used when applying doAntennaTemp               
    real(kind=4)      :: etaa        !    67  [----] Telescope aperture efficiency                                     
    real(kind=4)      :: hpbw        !    68  [rad ] Azimuthally-averaged half-power beam width                        
    character(len=8)  :: tempscal    ! 69-70  [str ] Temperature scale in use                                          
    real(kind=8)      :: lodopave    ! 71-72  [MHz ] Average LO frequency Doppler corrected to freqFrame (SPECSYS)     
    real(kind=4)      :: gim0        !    73  [----] Sideband gain polynomial coeff 0 applied                          
    real(kind=4)      :: gim1        !    74  [----] Sideband gain polynomial coeff 1 applied                          
    real(kind=4)      :: gim2        !    75  [----] Sideband gain polynomial coeff 2 applied                          
    real(kind=4)      :: gim3        !    76  [----] Sideband gain polynomial coeff 3 applied                          
    real(kind=4)      :: mixercurh   !    77  [mA  ] Calibrated mixer junction current, horizontal                     
    real(kind=4)      :: mixercurv   !    78  [mA  ] Calibrated mixer junction current, vertical                       
    character(len=28) :: datehcss    ! 79-85  [str ] Processing date (ISO date)                                        
    character(len=24) :: hcssver     ! 86-91  [str ] HCSS version                                                      
    character(len=16) :: calver      ! 92-95  [str ] Calibration version                                               
    real(kind=4)      :: level       !    96  [----] Pipeline level                                                    
  end type class_herschel_t
\end{verbatim}

%% \subsection{Multi-beam receiver}

%% (Section contents to be finalized dec 1997 - jan 1998 *PROPOSED* ).
%% \begin{verbatim}
%%       INTEGER MPIX
%%       PARAMETER (MPIX=64)
%%       INTEGER R_NPIX                ! number of pixels
%%       integer R_TYPEPIX             ! system code for pixel offsets
%%       REAL*4 R_TREC_PIX(MPIX)       ! Receiver temperature
%%       REAL*4 R_BEEFF_PIX(MPIX)      ! Beam efficiency
%%       REAL*4 R_FOEFF_PIX(MPIX)      ! Forward efficiency
%%       REAL*4 R_GAINI_PIX(MPIX)      ! Image/Signal gain ratio
%%       REAL*4 R_TSYS_PIX(MPIX)       ! System temperature
%%       REAL*4 R_COUNT_PIX(MPIX,3)    ! Power of Atm., Chopp., Cold
%%       REAL*4 R_TCHOP_PIX(MPIX)      ! Chopper temperature
%%       REAL*4 R_TCOLD_PIX(MPIX)      ! Cold load temperature
%%       REAL*4 R_LAM_PIX(MPIX)        ! Lambda offset
%%       REAL*4 R_BET_PIX(MPIX)        ! Beta offset
%% \end{verbatim}

%% \subsection{On-the-fly section receiver}

%% (Section contents to be finalized jan 1998 *PROPOSED* ).
%% \begin{verbatim}
%%       real*4 r_PA_OTF               ! position angle (E from N, rad., in basis)
%%       real*4 r_VEL_OTF              ! scanning speed (rad/sec)/
%%       real*4 r_integ_OTF            ! dump duration (aimed, sec.)
%% \end{verbatim}

%% \subsection{Pointing constants}

%% (Section contents to be finalized jan 1998 *PROPOSED* ).
%% \begin{verbatim}
%%       real*4 r_nule
%%       real*4 r_nula
%%       real*4 r_colstar
%% ! more  needed ?
%% \end{verbatim}

\subsection{Comment Section}

\begin{verbatim}
  integer(kind=4), parameter :: class_sec_com_id=-1
  integer(kind=4), parameter :: class_sec_comm_len=256
  type comment
     sequence
     integer(kind=4) :: ltext   ! Length of comment
     character ctext*1024       ! Comment string
  end type comment
\end{verbatim}

\subsection{Data Section}

The data section contains the spectrum intensity of each spectral channel.
It thus consists of NDATA real values.

\section{Old OTF data format}

This section is kept for backward compatibility. Indeed \class{} is still
able to read old OTF data format. Once \class{}, always \class{}...

\subsection{Data Section Descriptor}

\begin{verbatim}
  ! Data Section Descriptor.
  ! (Old OTF data format, kept for backward compatibility).
  integer(kind=4), parameter :: class_sec_desc_id=-30
  integer(kind=4), parameter :: class_sec_desc_len=4
  type descriptor
     sequence
     integer(kind=4) :: ndump   ! Number of records
     integer(kind=4) :: ldpar   ! Length of data header (longwords)
     integer(kind=4) :: ldatl   ! length of line data      (")
     integer(kind=4) :: ldump   ! length of record         (")
     integer(kind=4) :: rec     ! Current Record Number
  end type descriptor
\end{verbatim}

\subsection{Multiple spectra Data Section}

A multiple spectra data section contains many spectra, obtained in a
single observing scan: typically a drift scan across a source, a
on-off scan with a multi-beam receiver, or a drift scan with a
multi-beam receiver.  The data section is considered as a multiple
spectra data section is the special section `Data Section Descriptor'
is present.  It consists of a \verb|R_NDUMP| records (individual spectra);
each spectrum has \verb|R_LDATL| line channels, and is followed by a few data
associated parameters (\verb|R_LDPAR| words). The length of each record is thus 
\verb|R_LDUMP=R_LDPAR+R_LDATL|; the total length of the data section should be:
\verb|NDATA=R_NDUMP*R_LDUMP|.

The data header presently contains:
\begin{tabbing}
  ~~~~~~\= offset~~ \= type~~~~~~~~~~ \= name~~~~~~~~~~~~~~~~~ \=  \\
        \>  0       \> Integer*4      \>                       \> Record number  \\
        \>  1       \> Real*4         \> AZ                    \> [rad] Telescope Azimuth \\
        \>  2       \> Real*4         \> EL                    \> [rad] Telescope Elevation \\
        \>  3       \> Real*8         \> ST                    \> [rad] Sideral Time \\
        \>  5       \> Real*4         \> LAMOF                 \> [rad] Azimuth offset \\
        \>  6       \> Real*4         \> BETOF                 \> [rad] Elevation offset \\ 
        \> 12       \> Real*4         \> UT                    \> [sec] Universal Time in day DOBS \\
\end{tabbing}

%%         \>  7       \> Integer*4      \> R\_IPIX               \> Pixel number (multibeam)             *PROPOSED*\\
%%         \>  8       \> Real*4         \> R\_INTEG              \> actual dump integration time (sec.) *PROPOSED*\\
%%         \>  9       \> Real*4         \> R\_DOPEARTH           \> Vel Telesc/earth center  *PROPOSED*\\
%%         \> 10       \> Real*4         \> R\_DOPSUN             \> Vel Telesc/Sun          *PROPOSED*\\
%%         \> 11       \> Real*4         \> R\_DOPLSR             \> Vel Telesc/LSR          *PROPOSED*\\

\section{\class{} FITS format}

Starting with dec06 release of \gildas{}, the FITS support of \class{} is
largely improved. In addition to the old possibility of writing one
spectrum per file (see the ``Simple SPECTRUM Mode'' section), it is now
possible to read from the same FITS file, one spectrum in the primary
Header/Data Unit and collections of spectra in bintables stored as FITS
extensions (see the ``BINTABLE Mode''). Moreover, it is now possible to
store in the same bintable, spectra of different number of channels. The
distinction between the SPECTRUM and BINTABLE mode is mainly historical as
the software automatically toggles from one mode to another.

\subsection{Simple SPECTRUM Mode}

FITS headers written by \class{} depend on the informations present in the
corresponding \class{} headers. Any missing information will also be omitted
in FITS (and vice versa).  A typical FITS header written by \class{} looks
like this :

\begin{verbatim}
SIMPLE  =                    T         /
BITPIX  =                   32         /
NAXIS   =                    4         /                                (1)
NAXIS1  =                  921         /                                (2)
NAXIS2  =                    1         /                                (3)
NAXIS3  =                    1         /                                (3)
NAXIS4  =                    1         /                                (3)
BLANK   =           2147483647         / Blanking value
BSCALE  =  0.237091768440E-008         /
BZERO   =  0.192957639694E+001         /
DATAMIN = -0.316193056107E+001         /
DATAMAX =  0.702108430862E+001         /
BUNIT   = 'K (Ta*)'                    /                                (4)
CTYPE1  = 'FREQ    '                   /                                (5)
CRVAL1  =  0.000000000000E+000         / Offset frequency
CDELT1  =  0.390625000000E+005         / Frequency resolution
CRPIX1  =  0.578812988281E+003         /
CTYPE2  = 'RA---GLS'                   /                                (6)
CRVAL2  =  0.315384333333E+003         /
CDELT2  =  0.835555750817E-002         /
CRPIX2  =  0.000000000000E+000         /
CTYPE3  = 'DEC--GLS'                   /                                (6)
CRVAL3  =  0.681748750000E+002         /
CDELT3  =  0.505729609368E-002         /
CRPIX3  =  0.000000000000E+000         /
CTYPE4  = 'STOKES  '                   /                                (7)
CRVAL4  =      1.0000000000000         /
CDELT4  =      1.0000000000000         /
CRPIX4  =      0.0000000000000         /
TELESCOP= '30M'                        /
INSTRUME= '30ME0HUI-V01'               /
OBJECT  = 'NGC7023'                    /
RADESYS = 'FK5     '                   /
EQUINOX =  0.200000000000E+004         /                                (8)
LINE    = '13co10'                     / Line name                      (9)
RESTFREQ=  0.110201354300E+012         / Rest frequency                 (10)
VELO-LSR=  0.250000000000E+004         / [m/s] Velocity of referance ch (11)
SPECSYS = 'LSRK'                       / Reference frame
DELTAV  = -0.106265872717E+003         / Velocity spacing of channels   (12)
IMAGFREQ=  0.972814604954E+011         / Image frequency                (13)
TSYS    =  0.158461090088E+003         / System temperature             (14)
OBSTIME =  0.492044001818E+000         / Integration time               (15)
SCAN-NUM=  0.100000000000E+004         / Scan number                    (16)
TAU-ATM =  0.189633205533E+000         / Atmospheric opacity            (17)
NPHASE  =                    1         / Number of frequency phases     (18)
DELTAF1 =  0.000000000000E+000         / Frequency offset Phase 1       (19)
PTIME1  =  0.493851512671E+000         / Duration of Phase 1            (19)
WEIGHT1 =  0.000000000000E+000         / Weight of Phase 1              (19)
GAINIMAG=  0.501190014184E-001         / Image sideband gain ratio      (20)
BEAMEFF =  0.949999988079E+000         / Beam efficiency                (21)
FORWEFF =  0.949999988079E+000         / Image sideband gain ratio      (22)
ORIGIN  = 'CLASS-Grenoble  '           /
DATE    = '2023-11-07T00:00:00.000'    / Date written
TIMESYS = 'UTC             '           /
DATE-OBS= '2009-09-07T18:47:46.352'    / Date observed
DATE-RED= '2009-09-15T00:00:00.000'    / Date reduced
ELEVATIO=  0.486971109330E+002         /  Telescope elevation
AZIMUTH =  0.385419378637E+003         /  Telescope azimuth
UT      = ' 18:47:46.352'              /  Universal time at start
LST     = ' 17:42:05.449'              /  Sideral time at start
END
\end{verbatim}

\begin{enumerate}
\item Although only one axis is really necessary, it is very convenient to
  define four, use the first one for the channels, and the three last ones
  to code the positions and stokes parameters.
\item The first axis is used to define effectively the spectrum. Thus {\tt
    NAXIS1} is the number of channels.
\item {\tt NAXIS2, NAXIS3,} and {\tt NAXIS4} are all one for a single
  spectrum. Note however that it is possible to store a raster map with a
  similar header as this one.
\item Could be Janskys.
\item First axis defined in terms of frequency (in the signal sideband in
  case of double sideband operations). The frequency of a specific channel
  is given by \\
  {\tt F(i) = RESTFREQ + CRVAL1 + ( i - CRPIX1 ) * CDELT1}\\
  in which the Rest frequency {\tt RESTFREQ} is defined later in the
  header.
\item Second axis, Right Ascension RA (as in this case) or Galactic
  Longitude GLON. The information as presented here is slightly incomplete,
  since it would be in general necessary to have an information about the
  kind of projection used. On most radio telescopes, it is simply assumed
  that the angular offset in RA is divided by the cosine of Declination to
  represent ``true'' angular offsets (valid only for a small field). Small
  telescopes may need more elaborate projection systems. In the current
  example, the position really observed is \\
  {\tt Dec = CRVAL3 + ( 1 - CRPIX3 ) * CDELT3}\\
  {\tt Ra  = CRVAL2 + ( 1 - CRPIX2 ) * CDELT2 / COS(Dec)}\\
  That is, {\tt CDELT2} and {\tt CDELT3} represents angular offsets from
  the reference position {\tt (CRVAL2,CRVAL3)} in a Global Sinusoidal
  projection ({\tt RADIO} projection).
\item Stokes parameters as defined in the basic paper of Wells et al.
\item Equinox of these coordinates.
\item Molecular line name, for bookkeeping.
\item Rest frequency.
\item LSR Velocity of the reference channel. Heliocentric velocities can be
  used also.
\item Velocity spacings of the channels. This information is duplicate with
  the rest frequency and frequency spacing of channels, but convenient. The
  velocity of a given channel is thus given by\\
  {\tt V(i) = VLSR + ( i - CRPIX1 ) * DELTAV}
\item Image frequency, for double sideband operation.
\item System temperature, necessary for some kind of weighting when adding a number
  of spectra.
\item Integration time, used for the same reason as above.
\item Scan number, for bookkeeping.
\item Atmospheric opacity in the signal sideband.
\item For multi-phased spectra (i.e. frequency switching) number of phases.
\item For each phase, the frequency offset, the phase length and weight.
\item The ratio of gains in the image and signal sidebands (in case of
  double sideband operation).
\item The telescope beam efficiency.
\item The telescope forward efficiency.
\end{enumerate}

The FITS interface for Continuum data is still experimental. Try it, and
send your comments...

\subsection{BINTABLE Mode}

In addition to the simple ``one FITS file per Spectrum'' mode, \class{}
supports the Binary Table extension, in reading and writing.  \class{}
reads and processes all the Binary Table extension of the FITS file.  The
FITS keywords for the Binary Table format are similar to those of the
simple Spectrum mode, but a complete set of spectra can be handled in a
single binary table.

Keywords which would change from one spectrum to the next are handled as
Binary columns of the table, with the {\tt TTYPEi} FITS keyword holding the
name of the corresponding simple FITS keyword.  Reading such FITS files,
\class{} will support any value for the {\tt TTYPEi} keywords.  If a column
and a keyword have the same name, the column prevails.  If several columns
have the same name (try to avoid this), the last one prevails.

The spectra are handled in a {\tt SPECTRUM} column.

Here is an example with a tiny primary Header Data Unit and a Binary Table
extension header.
\begin{verbatim}
SIMPLE  =                    T
BITPIX  =                    8
NAXIS   =                    0
EXTEND  =                    T
BLOCKED =                    T
ORIGIN  = 'My observatory'
CREATOR = 'Me      '
END

...

XTENSION= 'BINTABLE'
BITPIX  =                    8         / Always 8.
NAXIS   =                    2         / Always 2: tables have 2 dimensions.
NAXIS1  =                 7275         / Number of bytes per row.
NAXIS2  =                    4         / Number of rows.
PCOUNT  =                    0         / Usually 0.
GCOUNT  =                    1         / Always 1.
TFIELDS =                   18         / Number of columns.
EXTNAME = 'MATRIX  '                   / Just a name, not important.
EXTVER  =                    1         / Always 1.
MAXIS   =                    4         / Number of dimensions in the data.
MAXIS1  =                 1793         / Dummy number of channels (see TTYPE1).
MAXIS2  =                    1         / 
MAXIS3  =                    1         / 
MAXIS4  =                    1         / 
CTYPE1  = 'FREQ    '                   / Dim1: freq => MAXIS1 = Nb channels.
CRVAL1  =  0.0000000000000E+00         / Frequency offset, always 0.
CDELT1  =  0.1000000000000E+07         / Frequency resolution [Hz].
CRPIX1  =  0.2565000000000E+03         / Dummy reference channel (see TTYPE18).
CTYPE2  = 'RA      '
CRVAL2  =  0.0000000000000E+00
CDELT2  =  0.0000000000000E+00
CRPIX2  =  0.0000000000000E+00
CTYPE3  = 'DEC     '
CRVAL3  =  0.0000000000000E+00
CDELT3  =  0.0000000000000E+00
CRPIX3  =  0.0000000000000E+00
CTYPE4  = 'STOKES  '
CRVAL4  =  0.0000000000000E+00
CDELT4  =  0.0000000000000E+00
CRPIX4  =  0.0000000000000E+00
SUBSCAN =                    1         / Subscan number.  Often 1.
LINE    = 'CS(5-4)     '               / Name of your line, up to 12 chars.
OBJECT  = 'JUPITER     '               / Name of your source, up to 12 chars.
RESTFREQ=  0.2449356430000E+12         / Rest (signal) frequency at ref chan.
VELO-HEL=  0.0000000000000E+00         / Velocity at ref.  chan [m.s-1].
VELDEF  = 'RADI-HEL'                   / Type of velocity.
GAINIMAG=  0.9999999776483E-02         / Ratio Image/Signal.
BEAMEFF =  0.8600000143051E+00         / Beam efficiency.
FORWEFF =  0.8600000143051E+00         / Forward efficiency.
EQUINOX =  0.1950000000000E+04         / Epoch of coordinates.
DATE-RED= '15/07/97'                   / Date of reduction.
TTYPE1  = 'MAXIS1  '                   / Number of channels for this spectrum.
TFORM1  = '1J      '                   / [integer]
TTYPE2  = 'SCAN    '                   / Scan number.
TFORM2  = '1J      '                   / [integer]
TTYPE3  = 'TELESCOP'                   / Name of your backend.
TFORM3  = '12A     '                   / [Up to 12 characters]
TTYPE4  = 'TSYS    '                   / System temperature.
TFORM4  = '1E      '                   / [K]
TTYPE5  = 'IMAGFREQ'                   / Image freq at ref channel.
TFORM5  = '1E      '                   / [Hz]
TTYPE5  = 'DELTAV  '                   / Velocity resolution.
TFORM6  = '1E      '                   / [m.s-1]
TTYPE7  = 'TAU-ATM '                   / Atmospheric opacity.
TFORM7  = '1E      '                   / [neper]
TTYPE8  = 'MH2O    '                   / Water vapor content.
TFORM8  = '1E      '                   / [mm]
TTYPE9  = 'TOUTSIDE'                   / Atm temperature near the antenna.
TFORM9  = '1E      '                   / [K]
TTYPE10 = 'PRESSURE'                   / Atm pressure on the antenna.
TFORM10 = '1E      '                   / [hPa]
TTYPE11 = 'ELEVATIO'                   / Elevation.
TFORM11 = '1E      '                   / [deg]
TTYPE12 = 'AZIMUTH '                   / Azimuth.
TFORM12 = '1E      '                   / [deg]
TTYPE13 = 'DATE-OBS'                   / Observing date.
TFORM13 = '23A     '                   / 'dd/mm/yy' for ex.  See below.
TTYPE14 = 'UT      '                   / Universal time at start.
TFORM14 = '1D      '                   / [s]
TTYPE15 = 'LST     '                   / Sideral time at start.
TFORM15 = '1D      '                   / [s]
TTYPE16 = 'OBSTIME '                   / Integration duration.
TFORM16 = '1E      '                   / [s]
TTYPE17 = 'SPECTRUM'                   / Your data.
TFORM17 = '1793E   '                   / [K]
TTYPE18 = 'CRPIX1  '                   / Reference channel for this spectrum.
TFORM18 = '1E      '                   / [real]
END
\end{verbatim}

\subsubsection{Known keywords}

\begin{description}
\item[\tt HISTORY] Some ``History'' comments.  Whether this information
  should be given with specific keywords or in an History record is still
  an open question.  This information is not really needed for further data
  reduction, but it helps bookkeeping.
\item[\tt MAXIS] Number of axes.
\item[\tt MAXISi] Number of elements for axis {\tt i}.  {\tt i} goes from 1
  to {\tt MAXIS}. The first axis is usually used to define effectively the
  spectrum.  Thus {\tt MAXIS1} corresponds to the number of channels. The
  second and third one can be used to define the coordinates of the spectra
  when storing a full spectra cube.
\item[\tt CTYPEi] The type of physical coordinate on axis i: frequency,
  velocity, right ascension, time... The unit of the values of {\tt CRVALi}
  and {\tt CRPIXi} are conventionnally defined by the value of the {\tt
    CTYPEi} keyword. The authorized values and associated units are
  \begin{description}
  \item[\tt FREQ or FREQUENCY] Frequency axis. Unit: [Hz].
    Frequency for channel number n is defined by:
    {\tt F(n) = RESTFREQ + CRVALi + ( n - CRPIXi ) * CDELTi}
  \item[\tt LAMBDA or WAVELENG] wavelength axis. Unit: [m$^{-1}$].
  \item[\tt VELO or FELO] Velocity axis. Unit: [m.s$^{-1}$].  {\tt
      VELO} corresponds to the radio convention while {\tt FELO} implies
    channels regularly gridded in frequency but expressed as velocity in
    the optical convention. \class{} will process {\tt VELO} and {\tt FELO}
    in the same way *** JP. The referential is defined by the second part
    of the keyword:
    \begin{itemize}
    \item {\tt -LSR} or {\tt CITY} or empty second part: Local Standart
      Rest velocity.
    \item {\tt -HEL} Heliocentric (barycentric) velocity.
    \item {\tt -OBS} or {\tt -TOP} The frame of rest of the
      observer/telescope (topocentric).
    \item {\tt -EAR} or {\tt -GEO} Geocentric velocity
    \end{itemize}
    The full keyword may thus looks like: {\tt FELO}, {\tt VELOCITY}, {\tt
      VELO-GEO}...
  \item[\tt RA- or RA] Right ascension axis. Unit: [degrees].
    Right Ascension {\tt RA} or galactic longitude {\tt GLON}: The
    information as presented here is slightly incomplete, since it would be
    in general necessary to have an information about the kind of
    projection used.  On most radio telescopes, it is simply assumed that
    the angular offset in {\tt RA} is divided by the cosine of Declination
    to represent ``true'' angular offsets (valid only for a small field).
    Small telescopes may need more elaborate projection systems.  In the
    current example, the position really observed is\\
    {\tt Dec = CRVAL3 + ( 1 - CRPIX3 ) * CDELT3}\\
    {\tt Ra = CRVAL2 + ( 1 - CRPIX2 ) * CDELT2 / COS(Dec)}
  \item[\tt DEC- or \tt DEC] Declination axis. Unit: [degrees].  Same
    caveats as for {\tt RA}.
  \item[\tt GLAT] Galactic latitude axis. Unit: [degrees].
    Same caveat as for {\tt RA}.
  \item[\tt GLON] Galactic longitude axis. Unit: [degrees].
    Same caveat as for {\tt RA}.
  \item[\tt TIME or UT or UTC] Time axis. Unit: [s].
    The time since {\tt DATE-OBS}.
  \item[\tt STOKES] Stokes axis. The Stokes parameter of the data as
    defined in the basic paper of Wells et al.
  \end{description}
\item[\tt CRPIXi] The array location of the reference pixel along axis i.
  {\tt CRPIXi}'s value may be a fractional number of pixels and/or outside
  of the limits of the array.  This descriptor is optional for degenerated
  axes (\ie{} 0 or 1 element).
\item[\tt CRVALi] The value of the physical coordinate on axis i at the
  reference pixel.
\item[\tt CDELTi] The increment in physical coordinates along axis i.  This
  descriptor is optional for degenerated axes.  Warning: Although there is
  a difference between the frequency spacing between channels and the
  frequency resolution of each channel, \class{} currently handles only one
  value for both.
\item[\tt BSCALE] A value of 1 is assumed when it is undefined. The
  following formula will be applied to the FITS data: %
  \verb|CLASS_spectrum = BZERO + BSCALE * FITS_spectrum|
\item[\tt BZERO] A value of 0 is assumed when it is undefined. The
  following formula will be applied to the FITS data: %
  \verb|CLASS_spectrum = BZERO + BSCALE * FITS_spectrum|
\item[\tt DATAMIN] Minimum value of your spectrum.  Understood by \class{}
  but unused.
\item[\tt DATAMAX] Maximum value of your spectrum.  Understood by \class{}
  but unused.
\item[\tt BLANK] Blanking value.  If not specified, -1e38 assumed.  {\tt
    BSCALE} and {\tt BZERO} are applied to the blanking value.
\item[\tt PCOUNT] Usually 0.  But if you provide your data as Variable
  Length arrays, then you will have to tweak this value which specifies the
  size of the variable length array heap, plus a gap (if any).  See {\tt
    THEAP}.
\item[\tt GCOUNT] Always 1 in Binary tables.
\item[\tt THEAP] Byte offset of heap area.  The heap area is where the
  Variable Length Array data are stored.  They are usually stored just
  after the binary table.  The heap does not need to begin at the end of
  the main table data.  The user may choose to provide a gap between the
  main table and the beginning of the heap.  The size of this gap, in
  bytes, can be found using the value of the {\tt THEAP} keyword.  If there
  is no gap, the value of {\tt THEAP} is {\tt NAXIS1 * NAXIS2}.  If there
  is, the size of the gap is the difference between the value of {\tt
    THEAP} and {\tt NAXIS1 * NAXIS2}. A value of {\tt NAXIS1 * NAXIS2}
  is assumed when this keyword is not defined.
\item[\tt BEAMEFF] The main-beam efficiency.
\item[\tt FORWEFF or ETAFSS] The forward spillover and scattering
  efficiency.
\item[\tt GAINIMAG] Ratio Image/Signal.
\item[\tt MH2O] Water vapor content. Unit: [mm].
\item[\tt PRESSURE] The ambiant atmospheric pressure. Unit: [hPa].
\item[\tt TOUTSIDE] The ambient temperature. Unit [K].
\item[\tt NPHASE] Value: from 1 to 8.  For multi-phased spectra (i.e.
  frequency switching) number of phases.
\item[\tt DELTAFj] Frequency offset for phase {\tt j}. Unit: [Hz].
\item[\tt PTIMEj] Duration of phase {\tt j}. Unit: [s].
\item[\tt WEIGHTj] Weight of phase {\tt j}.
\item[\tt TELESCOP] A string value giving the telescope name. \class{}
  truncates this string to the first 12 characters.  There is a \class{}
  convention about {\tt TELESCOP}, which is not mandatory: {\tt TELESCOP}
  contains the name of the instrument, the name of the backend and the name
  of the subband (if any).  For example: ``HIFI-HRV-02'' for subband 2 of
  the High Resolution Spectrometer (Vertical polarization) of HIFI (one of
  the Herschel's instrument).
\item[\tt AZIMUTH] The azimuth at {\tt TIME}. Unit: [degrees]. If the {\tt
    TIME} axis is non-degenerated, then this is the azimuth at the {\tt
    TIME} of the first pixel on the {\tt TIME} axis.
\item[\tt ELEVATIO] The elevation at {\tt TIME}. Unit: [degrees]. Same caveat as
  for {\tt AZIMUTH}.
\item[\tt DATE-OBS] or \verb|DATE_OBS| A string giving the observation date
  and optionally the time at the observation start using the new FITS y2k
  convention.  Since \class{} does not handle the {\tt TIMESYS} keyword yet
  to indicate the time system, UTC is assumed. Accepted formats are
  \begin{itemize}
  \item {\tt 'dd/mm/yy'} (1900 is added to yy)
  \item {\tt 'yyyy/mm/dd'}
  \item {\tt 'yyyy/mm/ddThh:mm:ss.s'}, where 'T' stands for the letter 'T'
    and has no meaning except as a separator between date and time.  The
    trailing time is ignored, you have to provide it through {\tt UT}, {\tt
      UTC} or {\tt LST}.
  \end{itemize}
\item [\tt DATE-RED] or \verb|DATE_RED| or {\tt DATE} Same as {\tt
    DATE-OBS}, except it is the date at which the data have been reduced.
\item[\tt SCAN or SCAN-NUM] or \verb|SCAN_NUM| Scan number of the
  observation, according to the \class{} terminology.
\item[\tt SUBSCAN] Subscan number of the observation, according to the
  \class{} terminology.
\item[\tt UT or UTC] Universal time of the start of the observation
  provided as a string or a number. Then the format is
  \begin{itemize}
  \item 'hh:mm:ss.s', when this keyword contains a string.
  \item The number of seconds after midnight, when this keyword contains a
    number.
  \end{itemize}
\item[\tt LST] Sideral time of the start of the observation.  See {\tt UT}
  or {\tt UTC} for information about the format.
\item[\tt OBSTIME or EXPOSURE] The effective integration time. Unit: [s].
  Necessary for some kind of weighting when adding several spectra.
\item[\tt TSYS] The system temperature. Unit: [K].  Necessary for some
  kind of weighting when adding several spectra.
\item[\tt TAU-ATM] or \verb|TAU_ATM| The opacity at {\tt RESTFREQ}.
  \class{} does not handle the opacity in the image band yet.
\item[\tt EQUINOX] The equinox of coordinates. Possible values are 1950
  or 2000.
\item[\tt OBJECT] A string value giving an object name. \class{} truncates
  this string to the first 12 characters.
\item[\tt LINE] Molecular line name, for bookkeeping. \class{} truncates
  this string to the first 12 characters.
\item[\tt RESTFREQ] The observed frequency at the reference pixel of
  the frequency-like axis. Unit: [Hz].
\item[\tt IMAGFREQ] The image sideband frequency corresponding to {\tt
    RESTFREQ} for double sideband operation. Unit: [Hz].
\item[\tt VELOCITY] The velocity at the reference channel.  The
  referential frame must be defined by {\tt VELDEF}.
\item[\tt VELO-LSR or VLSR] The velocity at the reference channel,
  understood as a velocity in the Local Standard Rest frame. Thus, you
  don't need to use {\tt VELDEF} to specify the frame. Unit: [m.s$^{-1}$].
\item[\tt VELO-OBS or VELO-TOP] The velocity at the reference channel,
  understood as a velocity in the frame of rest of the observer/telescope.
  Thus, you don't need to use {\tt VELDEF} to specify the frame. Unit:
  [m.s$^{-1}$].
\item[\tt VELO-HEL] The velocity at the reference channel,
  understood as a velocity in the Heliocentric (barycentric) frame.  Thus,
  you don't need to use {\tt VELDEF} to specify the frame. Unit: [m.s$^{-1}$].
\item[\tt VELO-EAR or VELO-GEO] The velocity at the reference
  channel, understood as a velocity in the Geocentric frame.  Thus, you
  don't need to use {\tt VELDEF} to specify the frame. Unit: [m.s$^{-1}$].
\item[\tt DELTAV or DELTAVEL] Velocity spacings of the channels.  This
  information is duplicate with the rest frequency and frequency spacing of
  channels, but convenient.  The velocity of a given channel n is thus
  given by {\tt V(n) = VLSR + (n - CRPIXi) * DELTAV}.  If {\tt DELTAV} is
  not provided, then the velocity resolution will be calculated by {\tt
    DELTAV = -c / RESTFREQ * CDELT1}, where {\tt CDELT1} is the frequency
  spacing and {\tt c} the celerity of light.
\item[\tt VELDEF] The velocity definition and frame (8 characters).  The
  first 4 characters describe the velocity definition.  Accepted
  definitions are
  \begin{itemize}
  \item {\tt RADI} Radio convention.
  \item {\tt OPTI} Optical convention.
  \item {\tt RELA} Relativistic convention.
  \end{itemize}
  \class{} only handles {\tt RADI} and does not handle {\tt OPTI} or {\tt
    RELA}.  If nothing specified, {\tt RADI} is assumed.  This is the list
  of the values \class{} understands
  \begin{itemize}
  \item {\tt LSR} or {\tt RADI-LSR} Local Standart Rest frame.
  \item {\tt HEL} or {\tt RADI-HEL} Heliocentric (barycentric) frame.
  \item {\tt OBS} or {\tt RADI-OBS} Frame of rest of the
    observer/telescope.  Note that {\tt TOP} is not handled here.
  \item {\tt EAR} or {\tt RADI-EAR} Geocentric frame.
  \end{itemize}
\end{description}

\subsubsection{Known columns and data storage}

Most of the keywords described above can be used as column names.  The
value contained in the column prevails on the value contained in the
Keyword.  This can be useful if you need to store spectra with different
lengths in your table: You can use a column named {\tt MAXIS1} containing
the number of channels for each spectrum of the table (the trailing bytes
requiered to fill the {\tt DATA} column will be ignored then).

Here is the list of authorized column names which can also be keywords:
{\tt DATAMAX}, {\tt DATAMIN}, {\tt MAXISi}, {\tt CRVALi}, {\tt CRPIXi},
{\tt CDELTi}, {\tt BEAMEFF}, {\tt FORWEFF} or {\tt ETAFSS}, {\tt GAINIMAG},
{\tt MH2O}, {\tt PRESSURE}, {\tt TOUTSIDE}, {\tt AZIMUTH}, {\tt ELEVATIO},
{\tt DATE-OBS} or \verb|DATE_OBS|, {\tt DATE-RED} or \verb|DATE_RED| or
{\tt DATE}, {\tt SCAN} or {\tt SCAN-NUM} or \verb|SCAN_NUM|, {\tt SUBSCAN},
{\tt LST}, {\tt UT} or {\tt UTC}, {\tt TAU-ATM} or \verb|TAU_ATM|, {\tt
  TELESCOP}, {\tt OBSTIME} or {\tt EXPOSURE}, {\tt TSYS}, {\tt EPOCH} or
{\tt EQUINOX}, {\tt OBJECT}, {\tt IMAGFREQ}, {\tt LINE}, {\tt RESTFREQ},
{\tt VELOCITY}, {\tt VELDEF}, {\tt VELO-LSR} or {\tt VLSR}, {\tt VELO-OBS}
or {\tt VELO-TOP}, {\tt VELO-HEL}, {\tt VELO-EAR} or {\tt VELO-GEO}, {\tt
  DELTAV} or {\tt DELTAVEL}.

The following names are exclusively used for columns because their value
(your data) can not be factorized
\begin{description}
\item[\tt MATRIX or SPECTRUM or SERIES or DATA] The spectra intensities.
  Unit: [K]. This is the only mandatory column. Indeed, why would we make a
  FITS file without any spectrum? This column must also be unique, \ie{}
  only one data column per binary table. The number of elements of this
  column, specified in {\tt TFORM}, depends on the way you want to store
  your data (Fixed or Variable Length Arrays). More explanations are
  available below
\item[\tt WAVE] Frequency of each channel. Unit: [Hz]. This column is
  optional.  If it is present, the spectra is considered as ``irregularly
  sampled'': each channel has its own frequency and the frequency axis
  description; In other words, the {\tt RESTFREQ}, {\tt CDELTi}, {\tt
    CRVALi}) keyword are not used at all even when present.  {\tt MAXISi}
  is still used, since it gives the number of channels. If this column is
  absent, then the frequency axis will be described in the usual way.
\end{description}
Both the {\tt SPECTRUM} and {\tt WAVE} columns must have the same number of
elements to obtain a meaningful result. Those columns may either be filled
with
\begin{description}
\item[Fixed Length Arrays] This may be the easiest way to store the data
  but it may waste a lot of space if you have spectra with different number
  of channels.  The steps are:
  \begin{enumerate}
  \item Description of the data column.
    \begin{enumerate}
    \item First, chose a precision: E for single precision floating point
      number (4 bytes), D for double precision floating point number (8
      bytes).
    \item Second, browse your collection of spectra to find the one with
      the highest number of channels (for example, 1793).
    \item Now you can give {\tt TFORMi} the value 1793E:
\begin{verbatim}
  TTYPE17 = 'SPECTRUM'
  TFORM17 = '1793E   '
\end{verbatim}
    \end{enumerate}
  \item Description of the number-of-channel column. If your collection of
    spectra contains spectra with different sizes, then you must specify
    the number of channels of each spectrum.  To do that, you have to use a
    column {\tt MAXISi}.  {\tt i} corresponds to the number of your
    frequency/wavelength/velocity axis: usually 1.
\begin{verbatim}
  TTYPE1  = 'MAXIS1  '
  TFORM1  = '1J      '
\end{verbatim}
    The values stored in this column will override the value given to the
    keyword {\tt MAXIS1} at the beginning of the bintable header.  If all
    your spectra have the same number of channels, then you don't need a
    {\tt MAXISi} column.  But make sure the {\tt MAXISi} keyword at the
    beginning of the bintable header has the correct value, which is the
    number of channels of your spectra.
  \item Description of the reference channel. You may like to chose
    the middle of your spectra to be the reference for frequencies.  If you
    have spectra with different sizes, then the reference channels may be
    different for each spectrum.  To override the value of the {\tt CRPIX1}
    keyword, you must use a {\tt CRPIX1} column.
\begin{verbatim}
  TTYPE18 = 'CRPIX1  '
  TFORM18 = '1E      '
\end{verbatim}
    If your reference channel is always the first one, you can forget about
    this column and give the value 1 to the {\tt CRPIX1} keyword.
  \item Filling the olumns. When it comes to fill the column with the data,
    make sure you are always storing 1793 (for example) elements.  If you
    are to store a spectrum with only 512 channels, then store those 512
    elements and pad with 1281 zeroes (single or double precision zeroes,
    not bytes).  You can see here the waste of space when you have a
    significant amount of spectra much smaller that the widest one.
  \end{enumerate}
\item[Variable Length Arrays] To save space and avoid filling half of your
  data columns with padding zeroes, you can use the Variable Length Array
  format described in the FITS standard (Definition of the Flexible Image
  Transport System, version 2.1b, section 8.3.5).
  
  The idea is not to store the data directly in the bintable records, but
  in the heap area located after the bintable.  In the {\tt SPECTRUM} or
  {\tt WAVE} column, you will find an Array Descriptor which basically
  consists in a pointer to a location in this heap area (an offset), and a
  number of elements to read.  By doing this, no padding is required any
  longer.  The FITS standart defines two kinds of Array Descriptors: P (two
  32bits unsigned integers values) and Q (two 64 bits unsigned integers
  values).  \class{} only handles P.
  
  You will not need a {\tt MAXIS1} column in this case, since the number of
  channels is written in the {\tt SPECTUM} column.  You may still need a
  {\tt CRPIX1} column to define your reference channels.
\end{description}

\subsection{Once FITS Always FITS}

This is the basic principle of FITS, but the IAU FITS committee keeps
making revisions of the FITS format. So things are expected to change, with
new versions of \class{} providing new FITS output, but still reading old
FITS.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
