[python-cpl] 01/04: New upstream prerelease 0.7beta

Ole Streicher olebole at moszumanska.debian.org
Wed Apr 22 11:46:22 UTC 2015


This is an automated email from the git hooks/post-receive script.

olebole pushed a commit to branch debian
in repository python-cpl.

commit f51d85db744f18733d6a927cc3527e2f3fc3d797
Author: Ole Streicher <olebole at debian.org>
Date:   Wed Apr 22 13:27:27 2015 +0200

    New upstream prerelease 0.7beta
---
 PKG-INFO                                           |   49 +-
 README                                             |   33 -
 README.rst                                         |   42 +
 cpl/CPL_library.c                                  |    3 +
 cpl/CPL_recipe.c                                   |   21 +-
 cpl/__init__.py                                    |   22 +-
 cpl/dfs.py                                         |  289 +++---
 cpl/logger.py                                      |    2 +-
 cpl/md5sum.py                                      |    2 +-
 cpl/result.py                                      |    4 +-
 cpl/version.py                                     |    3 +-
 doc/Makefile                                       |   89 ++
 doc/conf.py                                        |  202 ++++
 doc/dfs.rst                                        |    7 +
 doc/esorex.rst                                     |   41 +
 doc/frames.rst                                     |    7 +
 doc/index.rst                                      |   52 +
 doc/install.rst                                    |   82 ++
 doc/msg.rst                                        |   81 ++
 doc/parallel.rst                                   |   77 ++
 doc/param.rst                                      |    8 +
 doc/recipe.rst                                     |  132 +++
 doc/restrictions.rst                               |   51 +
 doc/result.rst                                     |  145 +++
 doc/tutorial.rst                                   |  162 ++++
 setup.py                                           |   38 +-
 test/TestRecipe.py                                 |  931 ++++++++++++++++++
 test/iiinstrumentp/.gitignore                      |   31 +
 test/iiinstrumentp/COPYING                         |  340 +++++++
 test/iiinstrumentp/Makefile.am                     |   32 +
 test/iiinstrumentp/acinclude.m4                    |  154 +++
 test/iiinstrumentp/bootstrap                       |  546 +++++++++++
 test/iiinstrumentp/configure.ac                    |   91 ++
 test/iiinstrumentp/iiinstrument/Makefile.am        |   42 +
 test/iiinstrumentp/iiinstrument/iiinstrument_dfs.c |   93 ++
 test/iiinstrumentp/iiinstrument/iiinstrument_dfs.h |   48 +
 .../iiinstrument/iiinstrument_pfits.c              |   88 ++
 .../iiinstrument/iiinstrument_pfits.h              |   44 +
 .../iiinstrument/iiinstrument_utils.c              |   80 ++
 .../iiinstrument/iiinstrument_utils.h              |   43 +
 test/iiinstrumentp/m4macros/cpl.m4                 |  363 +++++++
 test/iiinstrumentp/m4macros/eso.m4                 | 1019 ++++++++++++++++++++
 test/iiinstrumentp/recipes/Makefile.am             |   33 +
 test/iiinstrumentp/recipes/rtest.c                 |  463 +++++++++
 44 files changed, 5867 insertions(+), 218 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index 0f057c5..f3f5431 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,43 +1,24 @@
 Metadata-Version: 1.1
 Name: python-cpl
-Version: 0.6.2
-Summary: Python interface for the Common Pipeline Library.
-Home-page: https://pypi.python.org/pypi/python-cpl/0.6.2
+Version: 0.7
+Summary: Python interface for the ESO Common Pipeline Library
+Home-page: https://pypi.python.org/pypi/python-cpl/0.7
 Author: Ole Streicher
 Author-email: python-cpl at liska.ath.cx
 License: GPL
-Download-URL: http://pypi.python.org/packages/source/p/python-cpl/python-cpl-0.6.2.tar.gz
-Description: This module can list, configure and execute CPL-based recipes from Python.
-        The input, calibration and output data can be specified as FITS files
-        or as astropy.io.fits objects in memory.
+Download-URL: http://pypi.python.org/packages/source/p/python-cpl/python-cpl-0.7.tar.gz
+Description: This module can list, configure and execute CPL-based recipes from Python
+        (python2 and python3).  The input, calibration and output data can be
+        specified as FITS files or as ``astropy.io.fits`` objects in memory.
+        
+        The ESO `Common Pipeline Library <http://www.eso.org/sci/software/cpl/>`_
+        (CPL) comprises a set of ISO-C libraries that provide a comprehensive,
+        efficient and robust software toolkit. It forms a basis for the creation of
+        automated astronomical data-reduction tasks. One of the features provided by
+        the CPL is the ability to create data-reduction algorithms that run as plugins
+        (dynamic libraries). These are called "recipes" and are one of the main
+        aspects of the CPL data-reduction development environment.
         
-        The Common Pipeline Library (CPL) comprises a set of ISO-C libraries that
-        provide a comprehensive, efficient and robust software toolkit. It forms a
-        basis for the creation of automated astronomical data-reduction tasks. One of
-        the features provided by the CPL is the ability to create data-reduction
-        algorithms that run as plugins (dynamic libraries). These are called "recipes"
-        and are one of the main aspects of the CPL data-reduction development
-        environment. More information about the CPL can be found here:
-        
-          http://www.eso.org/sci/software/cpl/
-        
-        The interface may be used to run ESO pipeline recipes linked to CPL 
-        versions 4.0 to 6.3.1.
-        
-        Build instructions
-        ------------------
-        
-        python-cpl requires:
-        
-           - Python 2.6 or later
-           - astropy or pyfits
-        
-        python-cpl uses the standard Python distutils system to build and install
-        itself.  From the command line run::
-        
-            python setup.py install
-        
-        to install python-cpl.
 Platform: UNKNOWN
 Classifier: Development Status :: 4 - Beta
 Classifier: Intended Audience :: Science/Research
diff --git a/README b/README
deleted file mode 100644
index b24e255..0000000
--- a/README
+++ /dev/null
@@ -1,33 +0,0 @@
-Python interface for the Common Pipeline Library.
-
-This module can list, configure and execute CPL-based recipes from Python.
-The input, calibration and output data can be specified as FITS files
-or as astropy.io.fits objects in memory.
-
-The Common Pipeline Library (CPL) comprises a set of ISO-C libraries that
-provide a comprehensive, efficient and robust software toolkit. It forms a
-basis for the creation of automated astronomical data-reduction tasks. One of
-the features provided by the CPL is the ability to create data-reduction
-algorithms that run as plugins (dynamic libraries). These are called "recipes"
-and are one of the main aspects of the CPL data-reduction development
-environment. More information about the CPL can be found here:
-
-  http://www.eso.org/sci/software/cpl/
-
-The interface may be used to run ESO pipeline recipes linked to CPL 
-versions 4.0 to 6.3.1.
-
-Build instructions
-------------------
-
-python-cpl requires:
-
-   - Python 2.6 or later
-   - astropy or pyfits
-
-python-cpl uses the standard Python distutils system to build and install
-itself.  From the command line run::
-
-    python setup.py install
-
-to install python-cpl.
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..eb7f1d8
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,42 @@
+python-cpl
+==========
+
+*Python interface for the Common Pipeline Library*
+
+.. image:: https://pypip.in/v/python-cpl/badge.png
+    :target: https://pypi.python.org/pypi/python-cpl
+
+.. image:: https://pypip.in/d/python-cpl/badge.png
+    :target: https://pypi.python.org/pypi/python-cpl
+
+This module can list, configure and execute CPL-based recipes from Python
+(python2 and python3).  The input, calibration and output data can be
+specified as FITS files or as ``astropy.io.fits`` objects in memory.
+
+The ESO `Common Pipeline Library <http://www.eso.org/sci/software/cpl/>`_
+(CPL) comprises a set of ISO-C libraries that provide a comprehensive,
+efficient and robust software toolkit. It forms a basis for the creation of
+automated astronomical data-reduction tasks. One of the features provided by
+the CPL is the ability to create data-reduction algorithms that run as plugins
+(dynamic libraries). These are called "recipes" and are one of the main
+aspects of the CPL data-reduction development environment.
+
+Python-cpl releases are `registered on PyPI
+<http://pypi.python.org/pypi/python-cpl>`_, and development is occurring at
+the `project's github page <http://github.com/olebole/python-cpl>`_.
+
+For installation instructions, see the 
+`online documentation <http://cpl-python.readthedocs.org/en/master/install.html>`_
+or ``docs/install.rst`` in this source distribution.
+The travis test status, coverage, and documentation build status
+of the github repository is:
+
+.. image:: https://travis-ci.org/olebole/python-cpl.png
+    :target: https://travis-ci.org/olebole/python-cpl
+
+.. image:: https://coveralls.io/repos/olebole/python-cpl/badge.svg?branch=master
+  :target: https://coveralls.io/r/olebole/python-cpl?branch=master
+
+.. image:: https://readthedocs.org/projects/cpl-python/badge/?version=master
+    :target: https://readthedocs.org/projects/cpl-python/?badge=master
+
diff --git a/cpl/CPL_library.c b/cpl/CPL_library.c
index 9a1e035..3dd9541 100644
--- a/cpl/CPL_library.c
+++ b/cpl/CPL_library.c
@@ -4,6 +4,9 @@
 #include "CPL_library.h"
 
 unsigned long supported_versions[] = {
+    CPL_VERSION(6,6,0),
+    CPL_VERSION(6,5,1),
+    CPL_VERSION(6,5,0),
     CPL_VERSION(6,4,1),
     CPL_VERSION(6,4,0),
     CPL_VERSION(6,3,1),
diff --git a/cpl/CPL_recipe.c b/cpl/CPL_recipe.c
index 58097f5..ad35420 100644
--- a/cpl/CPL_recipe.c
+++ b/cpl/CPL_recipe.c
@@ -519,8 +519,13 @@ set_parameters(CPL_recipe *self, cpl_parameterlist *parameters, PyObject *parlis
 		self->cpl->parameter_set_string(par, PyString_AsString(value));
 	    }
 #else
-	    if (PyBytes_Check(value)) {
-		self->cpl->parameter_set_string(par, PyBytes_AsString(value));
+	    if (PyUnicode_Check(value)) {
+		PyObject* temp = PyUnicode_AsASCIIString(value);
+		if (temp != NULL) {
+		    self->cpl->parameter_set_string(par,
+						    PyBytes_AsString(temp));
+		    Py_XDECREF(temp);
+		}
 	    }
 #endif
 	} else if (type == self->cpl->TYPE_INT) {
@@ -555,9 +560,13 @@ set_environment(PyObject *runenv) {
 	    setenv(name, PyString_AsString(value), 1);
 	}
 #else
-	if (PyBytes_Check(value)) {
-	    setenv(name, PyBytes_AsString(value), 1);
-	} 
+	    if (PyUnicode_Check(value)) {
+		PyObject* temp = PyUnicode_AsASCIIString(value);
+		if (temp != NULL) {
+		    setenv(name, PyBytes_AsString(temp), 1);
+		    Py_XDECREF(temp);
+		}
+	    }
 #endif
 	if (value == Py_None) {
 	    unsetenv(name);
@@ -873,7 +882,9 @@ CPL_recipe_exec(CPL_recipe *self, PyObject *args) {
 	  self->cpl->memory_dump();
 	}
 	self->cpl->end();
+#ifdef HAVE_MTRACE
 	muntrace();
+#endif
 	_exit(retval);
     }
     
diff --git a/cpl/__init__.py b/cpl/__init__.py
index 03a5827..f93420c 100644
--- a/cpl/__init__.py
+++ b/cpl/__init__.py
@@ -1,10 +1,24 @@
+'''Python interface for the Common Pipeline Library
+
+This module can list, configure and execute CPL-based recipes from Python
+(python2 and python3).  The input, calibration and output data can be
+specified as FITS files or as ``astropy.io.fits`` objects in memory.
+
+The ESO `Common Pipeline Library <http://www.eso.org/sci/software/cpl/>`_
+(CPL) comprises a set of ISO-C libraries that provide a comprehensive,
+efficient and robust software toolkit. It forms a basis for the creation of
+automated astronomical data-reduction tasks. One of the features provided by
+the CPL is the ability to create data-reduction algorithms that run as plugins
+(dynamic libraries). These are called "recipes" and are one of the main
+aspects of the CPL data-reduction development environment.
+'''
+
 from __future__ import absolute_import
 
 from .version import version as __version__
 from .version import author as __author__
 from .version import email as __email__
 from .version import license_ as __license__
-from .version import doc as __doc__
 
 from .recipe import Recipe
 from .param import Parameter
@@ -18,4 +32,8 @@ Recipe.dir = '.'
 cpl_versions = [ '%i.%i.%i' % ver for ver in CPL_recipe.cpl_versions() ]
 del CPL_recipe
 del absolute_import
-del recipe, version, param, frames, result, ver, md5sum
+del recipe, version, param, frames, result, md5sum
+try:
+    del ver
+except NameError:
+    pass
diff --git a/cpl/dfs.py b/cpl/dfs.py
index bee5953..ec3cb18 100644
--- a/cpl/dfs.py
+++ b/cpl/dfs.py
@@ -1,3 +1,4 @@
+import itertools
 import os
 import sys
 try:
@@ -8,8 +9,8 @@ except:
 import cpl
 
 class ProcessingInfo(object):
-    '''This class contains support for reading input files and parameters from
-    the FITS header of a CPL processed file.
+    '''Support for reading input files and parameters from the FITS
+    header of a CPL processed file.
 
     This is done through the FITS headers that were written by the DFS function
     called within the processing recipe.
@@ -86,36 +87,41 @@ class ProcessingInfo(object):
           accompanied with the MD5 sum.
     '''
 
-    def __init__(self, source, datapaths = None):
+    def __init__(self, source, recno = -1, md5sums = None):
         '''
         :param source: Object pointing to the result file header
         :type source: :class:`str` or :class:`astropy.io.fits.HDUList`
                       or :class:`astropy.io.fits.PrimaryHDU` or 
                       :class:`astropy.io.fits.Header`
-        :param datapaths: Dictionary with frame tags as keys and directory paths
+        :param recno: Record number. Optional. If not given, the last record
+                      (with the highest record number) is used.
+        :type recno: :class:`int`
+        :param md5sums: Dictionary with md5 sums as keys and complete file names
                         as values to provide a full path for the raw and
                         calibration frames. Optional.
-        :type datapaths: :class:`dict`
+        :type md5sums: :class:`dict`
         '''
         if isinstance(source, str):
             header = fits.open(source)[0].header
-        elif isinstance(source, fits.HDUList):
+        elif isinstance(source, (fits.HDUList, list)):
             header = source[0].header
         elif isinstance(source, fits.PrimaryHDU):
             header = source.header
         elif isinstance(source, (fits.Header, dict)):
             header = source
         else:
-            raise ValueError('Cannot assign type %s to header' % 
-                             source.__class__.__name__)
+            raise ValueError('Cannot assign type {0} to header'.format(
+                    source.__class__.__name__))
         
-        self.name = header['HIERARCH ESO PRO REC1 ID']
+        if recno < 0:
+            for reccnt in range(1, 2**16):
+                if 'HIERARCH ESO PRO REC{0} ID'.format(reccnt) not in header:
+                    break
+            recno += reccnt
+        self.name = header['HIERARCH ESO PRO REC{0} ID'.format(recno)]
         self.product = header['HIERARCH ESO PRO CATG']
         self.orig_filename = header['PIPEFILE']
-        if datapaths and self.product in datapaths:
-            self.orig_filename = os.path.join(datapaths[self.product], 
-                                              self.orig_filename)
-        pipe_id = header.get('HIERARCH ESO PRO REC1 PIPE ID')
+        pipe_id = header.get('HIERARCH ESO PRO REC{0} PIPE ID'.format(recno))
         if pipe_id:
             self.pipeline, version = pipe_id.split('/')
             num_version = 0
@@ -125,37 +131,53 @@ class ProcessingInfo(object):
         else:
             self.pipeline =  None
             self.version = None
-        self.cpl_version = header.get('HIERARCH ESO PRO REC1 DRS ID')
+        self.cpl_version = header.get('HIERARCH ESO PRO REC{0} DRS ID'.format(recno))
         self.md5sum = header.get('DATAMD5')
+        if md5sums and self.md5sum in md5sums:
+            self.orig_filename = md5sums[self.md5sum]
         self.md5sums = {}
-        self.calib = _get_rec_keys(header, 'CAL', 'CATG', 'NAME', datapaths)
-        for cat, md5 in _get_rec_keys(header, 'CAL', 'CATG', 'DATAMD5').items():
+        self.calib = ProcessingInfo._get_rec_keys(header, recno, 'CAL', 'CATG', 'NAME')
+        for cat, md5 in ProcessingInfo._get_rec_keys(header, recno, 'CAL', 'CATG',
+                                                     'DATAMD5').items():
             if isinstance(md5, list):
-                for m, f in zip(md5, self.calib[cat]):
-                    if m is not None: 
-                        self.md5sums[f] = m
+                for i, m in enumerate(md5):
+                    if m is not None:
+                        if md5sums and m in md5sums:
+                            self.calib[cat][i] = md5sums[m]
+                        self.md5sums[self.calib[cat][i]] = m
             elif md5 is not None:
+                if md5sums and md5 in md5sums:
+                    self.calib[cat] = md5sums[md5]
                 self.md5sums[self.calib[cat]] = md5
-        raw = _get_rec_keys(header, 'RAW', 'CATG', 'NAME', datapaths)
+        raw = ProcessingInfo._get_rec_keys(header, recno, 'RAW', 'CATG', 'NAME')
         if raw:
             self.tag = list(raw.keys())[0]
             self.raw = raw[self.tag]
-            md5 = _get_rec_keys(header, 'RAW', 'CATG', 'DATAMD5')[self.tag]
+            md5 = ProcessingInfo._get_rec_keys(header, recno, 'RAW', 'CATG',
+                                               'DATAMD5')[self.tag]
             if isinstance(md5, list):
-                for m, f in zip(md5, self.raw):
+                for i, m in enumerate(md5):
                     if m is not None: 
-                        self.md5sums[f] = m
+                        if md5sums and m in md5sums:
+                            self.raw[i] = md5sums[m]
+                        self.md5sums[self.raw[i]] = m
             elif md5 is not None:
+                if md5sums and md5 in md5sums:
+                    self.raw = md5sums[md5]
                 self.md5sums[self.raw] = md5
         else:
             self.tag = None
+            self.raw = None
             self.input = None
-        param = _get_rec_keys(header, 'PARAM', 'NAME', 'VALUE')
+        param = ProcessingInfo._get_rec_keys(header, recno, 'PARAM', 'NAME', 'VALUE')
         self.param = dict()
         for k,v in param.items():
-            self.param[k] = _best_type(v)
+            self.param[k] = ProcessingInfo._best_type(v)
             
     def create_recipe(self):
+        '''Create a recipe and configure it with the parameters, calibration frames,
+        and the input tag. The recipe version will be the latest available one.
+        '''
         recipe = cpl.Recipe(self.name)
         recipe.param = self.param
         recipe.calib = self.calib
@@ -163,125 +185,146 @@ class ProcessingInfo(object):
         return recipe
 
     def create_script(self, scriptfile = sys.stdout):
+        '''Create a sample script that creates the recipe, configures it with
+        the parameters, calibration frames and input tags, and finally
+        starts the recipe.
+        '''
         if isinstance(scriptfile, str):
             scriptfile = file(scriptfile, mode='w')
         scriptfile.write('import cpl\n\n')
-        scriptfile.write('# Recipe: %s.%s, Version %s, CPL version %s\n' % 
-                         (self.pipeline, self.name, self.version[1], 
-                          self.cpl_version))
-        scriptfile.write('%s = cpl.Recipe(%s, version = %s)\n' % 
-                         (self.name, repr(self.name), repr(self.version[0])))
+        scriptfile.write('# Recipe: {0}.{1}, Version {2}, CPL version {3}\n'.format(
+                self.pipeline, self.name, self.version[1], self.cpl_version))
+        scriptfile.write('{0} = cpl.Recipe({1}, version = {2})\n'.format(
+                self.name, repr(self.name), repr(self.version[0])))
         scriptfile.write('\n# Parameters:\n')
         for k,v in self.param.items():
-            scriptfile.write('%s.param.%s = %s\n' % (self.name, k, repr(v)))
+            scriptfile.write('{0}.param.{1} = {2}\n'.format(self.name, k, repr(v)))
         if self.calib:
             scriptfile.write('\n# Calibration frames:\n')
         for k,v in self.calib.items():
-            scriptfile.write('%s.calib.%s = %s\n' % (self.name, k, repr(v)))
+            scriptfile.write('{0}.calib.{1} = {2}\n'.format(self.name, k, repr(v)))
         scriptfile.write('\n# Process input frames:\n')
-        scriptfile.write('%s.tag = %s\n' % (self.name, repr(self.tag)))
-        scriptfile.write('res = %s(%s)\n' % (self.name, repr(self.raw)))
-        scriptfile.write('%s = res.%s\n' % (self.product.lower(), self.product))
-        scriptfile.write('%s.writeto(%s)\n' % (self.product.lower(), 
-                                               repr(self.orig_filename)))
-
-    def printinfo(self):
-        print('Recipe: %s, Version %s, CPL version %s ' % (
-                self.name, self.version, self.cpl_version))
-        print('Parameters:')
+        scriptfile.write('{0}.tag = {1}\n'.format(self.name, repr(self.tag)))
+        scriptfile.write('res = {0}({1})\n'.format(self.name, repr(self.raw)))
+#        scriptfile.write('{0} = res.{1}\n'.format(self.product.lower(), self.product))
+#        scriptfile.write('{0}.writeto({1})\n'.format(self.product.lower(),
+#                                                     repr(self.orig_filename)))
+
+    def __str__(self):
+        s = 'Recipe: {0}, Version {1}, CPL version {2}\n'.format(
+            self.name, self.version, self.cpl_version)
+        s += 'Parameters:\n'
         for k,v in self.param.items():
-            print(' %s.%s.%s = %s' % (self.pipeline, self.name, k, v))
+            s += ' {0}.{1}.{2} = {3}\n'.format(self.pipeline, self.name, k, v)
         if self.calib:
-            print('Calibration frames:')
+            s += 'Calibration frames:\n'
         for k,v in self.calib.items():
-            if isinstance(v, str):
-                print(' %s %s' % (v,k))
+            if isinstance(v, (str, unicode)):
+                s += ' {0} {1}\n'.format(v,k)
             else:
+                m = max(len(n) for n in v)
                 for n in v:
-                    print(' %s %s' % (n,k))
-        print('Input frames:')
-        if isinstance(self.raw, str):
-            print(' %s %s' % (self.raw, self.tag))
-        else:
-            for n in self.raw:
-                print(' %s %s' % (n, self.tag))
-
-def _get_rec_keys(header, key, name, value, datapaths = None):
-    '''Get a dictionary of key/value pairs from the DFS section of the
-    header.
-
-    :param key: Common keyword for the value. Usually 'PARAM' for 
-                parameters, 'RAW' for raw frames, and 'CAL' for 
-                calibration frames.
-    :type key: :class:`str`
-    :param name: Header keyword (last part) for the name of each key
-    :type name: :class:`str`
-    :param value: Header keyword (last part) for the value of each key
-    :type name: :class:`str`
-    :param datapaths: Dictionary with frame tags as keys and directory paths 
-                    as values to provide a full path for the raw and 
-                    calibration frames. Optional.
-    :type datapaths: :class:`dict`
-
-    When the header
-    
-      HIERARCH ESO PRO REC1 PARAM1 NAME = 'nifu'
-      HIERARCH ESO PRO REC1 PARAM1 VALUE = '1'
-      HIERARCH ESO PRO REC1 PARAM2 NAME = 'combine'
-      HIERARCH ESO PRO REC1 PARAM2 VALUE = 'median'
-      
-    is called with
+                    s += ' {0:<{width}} {1}\n'.format(n, m, k, width = m)
+        if self.raw is not None:
+            s += 'Input frames:\n'
+            if isinstance(self.raw, (str, unicode)):
+                s += ' {0} {1}\n'.format(self.raw, self.tag)
+            else:
+                m = max(len(n) for n in self.raw)
+                for n in self.raw:
+                    s += ' {0:<{width}} {1}\n'.format(n, self.tag, width = m)
+        return s
 
-      _get_rec_keys('PARAM', 'NAME', 'VALUE')
+    def printinfo(self):
+        '''Print the recipe information to standard output.
+        '''
+        print(str(self))
 
-    the returned dictionary will contain the keys
+    @staticmethod
+    def _get_rec_keys(header, recno, key, name, value):
+        '''Get a dictionary of key/value pairs from the DFS section of the
+        header.
 
-      res['nifu'] = '1'
-      res['combine'] = 'median'
-    '''
-    res = dict()
-    for i in range(1, 2**16):
-        try:
-            prefix = 'HIERARCH ESO PRO REC1 %s%i' % (key, i)
-            k = header['%s %s' % (prefix, name)]
-            fn = header.get('%s %s' % (prefix, value))
-            if datapaths and k in datapaths:
-                fn = os.path.join(datapaths[k], fn)
-            if k not in  res:
-                res[k] = fn
-            elif isinstance(res[k], list):
-                res[k].append(fn)
-            else:
-                res[k] = [ res[k], fn ]
-        except KeyError:
-            break
-    return res
-    
-def _best_type(value):
-    '''Convert the value to the best applicable type: :class:`int`, 
-    :class:`float`, :class:`bool` or :class`str`.
+        :param key: Common keyword for the value. Usually 'PARAM' for
+                    parameters, 'RAW' for raw frames, and 'CAL' for
+                    calibration frames.
+        :type key: :class:`str`
+        :param recno: Record number.
+        :type recno: :class:`int`
+        :param name: Header keyword (last part) for the name of each key
+        :type name: :class:`str`
+        :param value: Header keyword (last part) for the value of each key
+        :type name: :class:`str`
 
-    :param value: Value to convert.
-    :type value: :class:`str`
-    '''
-    for t in int, float:
-        try:
-            return t(value)
-        except ValueError:
-            pass
-    return {'true':True, 'false':False}.get(value, value)
+        When the header
+
+          HIERARCH ESO PRO REC1 PARAM1 NAME = 'nifu'
+          HIERARCH ESO PRO REC1 PARAM1 VALUE = '1'
+          HIERARCH ESO PRO REC1 PARAM2 NAME = 'combine'
+          HIERARCH ESO PRO REC1 PARAM2 VALUE = 'median'
+
+        is called with
+
+          ProcessingInfo._get_rec_keys(1, 'PARAM', 'NAME', 'VALUE')
+
+        the returned dictionary will contain the keys
+
+          res['nifu'] = '1'
+          res['combine'] = 'median'
+        '''
+        res = dict()
+        for i in range(1, 2**16):
+            try:
+                prefix = 'HIERARCH ESO PRO REC{0} {1}{2}'.format(recno, key, i)
+                k = header['{0} {1}'.format(prefix, name)]
+                fn = header.get('{0} {1}'.format(prefix, value))
+                if k not in  res:
+                    res[k] = fn
+                elif isinstance(res[k], list):
+                    res[k].append(fn)
+                else:
+                    res[k] = [ res[k], fn ]
+            except KeyError:
+                break
+        return res
+
+    @staticmethod
+    def _best_type(value):
+        '''Convert the value to the best applicable type: :class:`int`,
+        :class:`float`, :class:`bool` or :class`str`.
+
+        :param value: Value to convert.
+        :type value: :class:`str`
+        '''
+        for t in int, float:
+            try:
+                return t(value)
+            except ValueError:
+                pass
+        return {'true':True, 'false':False}.get(value, value)
+
+    @staticmethod
+    def list(source, md5sums = None):
+        '''Get a list of all `ProcessingInfo` objects in the FITS header. The
+        list is sorted by the execution order.
+
+        :param source: Object pointing to the result file header
+        :type source: :class:`str` or :class:`astropy.io.fits.HDUList`
+                      or :class:`astropy.io.fits.PrimaryHDU` or
+                      :class:`astropy.io.fits.Header`
+        '''
+        pi = []
+        for i in range(1, 2**16):
+            try:
+                pi.append(ProcessingInfo(source, i, md5sums))
+            except KeyError:
+                break
+        return pi
 
 if __name__ == '__main__':
     import sys
 
-    datapaths = {
-        'BIAS':'raw', 'DARK':'raw', 'FLAT':'raw', 'ARC':'raw', 'OBJECT':'raw', 
-        'LINE_CATALOG':'aux', 'TRACE_TABLE':'aux', 'GEOMETRY_TABLE':'aux',
-        'MASTER_BIAS':'result', 'MASTER_DARK':'result', 'MASTER_FLAT':'result',
-        'WAVECAL_TABLE':'result', 'PIXTABLE_OBJECT':'result', 
-        }
     for arg in sys.argv[1:]:
-        print('---------------------')
-        print('file: %s' % arg)
-        pi = ProcessingInfo(arg, datapaths = datapaths)
+        print('{0}\nfile: {1}'.format('-' * 72, arg))
+        pi = cpl.dfs.ProcessingInfo(arg)
         pi.printinfo()
diff --git a/cpl/logger.py b/cpl/logger.py
index 184659b..7129ac9 100644
--- a/cpl/logger.py
+++ b/cpl/logger.py
@@ -46,7 +46,7 @@ class LogServer(threading.Thread):
                 line = logfile.readline()
                 os.remove(self.logfile)
                 while line:
-                    self.log(line)
+                    self.log(str(line.decode('ascii')))
                     line = logfile.readline()
         except:
             pass
diff --git a/cpl/md5sum.py b/cpl/md5sum.py
index f498282..90df125 100644
--- a/cpl/md5sum.py
+++ b/cpl/md5sum.py
@@ -17,5 +17,5 @@ def verify_md5(hdulist):
 
 def update_md5(hdulist):
     sum = datamd5(hdulist)
-    hdulist[0].header.update('DATAMD5', sum, 'MD5 checksum')
+    hdulist[0].header['DATAMD5'] =  (sum, 'MD5 checksum')
     return sum
diff --git a/cpl/result.py b/cpl/result.py
index 767f964..86f90c6 100644
--- a/cpl/result.py
+++ b/cpl/result.py
@@ -323,7 +323,7 @@ class RecipeCrash(Exception):
                     e.func))
             if os.path.exists(e.filename) and e.line:
                 logc.error('    %s\n' 
-                           % file(e.filename).readlines()[e.line-1].strip())
+                           % open(e.filename).readlines()[e.line-1].strip())
             if e.params:
                 logc.error('  Parameters:')
                 for p, v in e.params.items():
@@ -346,7 +346,7 @@ class RecipeCrash(Exception):
                                              else '', 
                                              e.func)
             if os.path.exists(e.filename) and e.line:
-                s += '    %s\n' % file(e.filename).readlines()[e.line-1].strip()
+                s += '    %s\n' % open(e.filename).readlines()[e.line-1].strip()
         s += RecipeCrash.signals.get(self.signal, '%s: Unknown' % str(self.signal))
         return s
 
diff --git a/cpl/version.py b/cpl/version.py
index 9bf9f8c..3df35ae 100644
--- a/cpl/version.py
+++ b/cpl/version.py
@@ -1,5 +1,4 @@
-version = '0.6.2'
+version = '0.7'
 author = 'Ole Streicher'
 email = 'python-cpl at liska.ath.cx'
 license_ = 'GPL'
-doc = 'Python interface for the Common Pipeline Library.\nThis module can list, configure and execute CPL-based recipes from Python.\nThe input, calibration and output data can be specified as FITS files\nor as astropy.io.fits objects in memory.\n\nThe Common Pipeline Library (CPL) comprises a set of ISO-C libraries that\nprovide a comprehensive, efficient and robust software toolkit. It forms a\nbasis for the creation of automated astronomical data-reduction tasks. One of\nthe features  [...]
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..8a48b86
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,89 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = PYTHONPATH=.. sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html      to make standalone HTML files"
+	@echo "  dirhtml   to make HTML files named index.html in directories"
+	@echo "  pickle    to make pickle files"
+	@echo "  json      to make JSON files"
+	@echo "  htmlhelp  to make HTML files and a HTML help project"
+	@echo "  qthelp    to make HTML files and a qthelp project"
+	@echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  changes   to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck to check all external links for integrity"
+	@echo "  doctest   to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/None.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/None.qhc"
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+	      "run these through (pdf)latex."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/doc/conf.py b/doc/conf.py
new file mode 100644
index 0000000..df2942c
--- /dev/null
+++ b/doc/conf.py
@@ -0,0 +1,202 @@
+# -*- coding: utf-8 -*-
+#
+# Python bindings for CPL recipes documentation build configuration file, created by
+# sphinx-quickstart on Sat Aug 28 10:52:22 2010.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+import cpl
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.append(os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx' ]
+# , 'cpl.cplsphinx'
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Python bindings for CPL recipes'
+copyright = u'2010-2015, Ole Streicher'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The full version, including alpha/beta/rc tags.
+release = cpl.__version__
+# The short X.Y version.
+#version = '0.7'
+version=release
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+html_title = "Python bindings for CPL"
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+#html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+html_show_sourcelink = False
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'python-cpl'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+latex_paper_size = 'a4'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '11pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'python-cpl-%s.tex' % version, u'Python bindings for CPL recipes',
+   u'Ole Streicher', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+latex_use_modindex = False
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'http://docs.python.org/': None, 'http://docs.astropy.org/':None}
+
diff --git a/doc/dfs.rst b/doc/dfs.rst
new file mode 100644
index 0000000..adc8a69
--- /dev/null
+++ b/doc/dfs.rst
@@ -0,0 +1,7 @@
+:mod:`cpl.dfs` DFS header parsing
+=================================
+
+.. automodule:: cpl.dfs
+
+.. autoclass:: ProcessingInfo
+.. automethod:: ProcessingInfo.__init__
diff --git a/doc/esorex.rst b/doc/esorex.rst
new file mode 100644
index 0000000..6f6de49
--- /dev/null
+++ b/doc/esorex.rst
@@ -0,0 +1,41 @@
+.. module:: cpl
+
+:mod:`cpl.esorex` :program:`EsoRex` legacy support
+==================================================
+
+.. automodule:: cpl.esorex
+
+Support for configuration and SOF files
+---------------------------------------
+
+.. autofunction:: init
+.. autofunction:: load_rc
+.. autofunction:: load_sof
+
+Convienence logging control
+---------------------------
+
+.. autodata:: msg
+
+   The following attributes control the format of the terminal messages:
+
+   .. autoattribute:: cpl.esorex.CplLogger.level
+   .. autoattribute:: cpl.esorex.CplLogger.format
+   .. autoattribute:: cpl.esorex.CplLogger.time
+   .. autoattribute:: cpl.esorex.CplLogger.component
+   .. autoattribute:: cpl.esorex.CplLogger.threadid
+
+.. autodata:: log
+
+   The following attributes control the format of the log file messages:
+
+   .. autoattribute:: cpl.esorex.CplLogger.filename
+   .. attribute:: CplLogger.dir
+
+      Directory name that is prepended to the log file name.
+
+   .. autoattribute:: cpl.esorex.CplLogger.level
+   .. autoattribute:: cpl.esorex.CplLogger.format
+   .. autoattribute:: cpl.esorex.CplLogger.time
+   .. autoattribute:: cpl.esorex.CplLogger.component
+   .. autoattribute:: cpl.esorex.CplLogger.threadid
diff --git a/doc/frames.rst b/doc/frames.rst
new file mode 100644
index 0000000..41df3ff
--- /dev/null
+++ b/doc/frames.rst
@@ -0,0 +1,7 @@
+.. module:: cpl
+
+The :class:`cpl.FrameConfig` class
+==================================
+
+.. autoclass:: FrameConfig
+.. seealso:: :attr:`Recipe.calib`
diff --git a/doc/index.rst b/doc/index.rst
new file mode 100644
index 0000000..53bb172
--- /dev/null
+++ b/doc/index.rst
@@ -0,0 +1,52 @@
+.. Python bindings for CPL recipes documentation master file, created by
+   sphinx-quickstart on Sat Aug 28 10:52:22 2010.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+.. title:: The CPL recipe python interface
+
+The CPL recipe python interface
+###############################
+
+.. module:: cpl
+
+This is a non-official python module to access CPL recipes. It is not meant as
+part of the CPL or the MUSE pipeline software, but may be useful for testing
+and analysis.
+
+.. seealso:: http://www.eso.org/sci/software/cpl
+
+"The Common Pipeline Library (CPL) consists of a set of C libraries, which have
+been developed to standardise the way VLT instrument pipelines are built, to
+shorten their development cycle and to ease their maintenance.  The Common
+Pipeline Library was not designed as a general purpose image processing
+library, but rather to address two primary requirements. The first of these
+was to provide an interface to the VLT pipeline runtime- environment. The
+second was to provide a software kit of medium-level tools, which allows
+astronomical data-reduction tasks to be built rapidly."
+[`ESO <http://www.eso.org/sci/software/cpl/introduction.html>`_]
+
+.. toctree::
+   :numbered:
+
+   install
+   tutorial
+   recipe
+   parallel
+   param
+   frames
+   result
+   msg
+   esorex
+   dfs
+   restrictions
+
+
+Feedback
+========
+
+Bug reports should be made on the `developer web page
+<http://github.com/olebole/python-cpl/issues>`_. Send python specific questions to
+python-cpl at liska.ath.cx. Questions regading CPL should be mailed to
+cpl-help at eso.org.
+
diff --git a/doc/install.rst b/doc/install.rst
new file mode 100644
index 0000000..806db60
--- /dev/null
+++ b/doc/install.rst
@@ -0,0 +1,82 @@
+Installation
+============
+
+Prequisites
+-----------
+
+* `Python <http://www.python.org/>`_ 2.6 or higher, 
+* `Astropy <http://www.astropy.org/>`_ or 
+  `Pyfits <http://packages.python.org/pyfits/>`_
+
+Binary packages
+---------------
+
+On Debian and debian-based systems (Ubuntu, Mint), python-cpl can be installed with the command
+
+.. code-block:: sh
+
+  apt-get install python-cpl
+
+Python CPL comes with the Ubuntu distribution since 12.04.
+Debian packages are in `Wheezy (Debian 7) <http://packages.debian.org/wheezy/python-cpl>`_, 
+`Wheezy (Debian 8) <http://packages.debian.org/jessie/python-cpl>`_, and 
+`Testing <http://packages.debian.org/testing/python-cpl>`_
+
+Source code
+-----------
+
+* `Python Package Index <http://pypi.python.org/pypi/python-cpl/>`_
+
+* `Git repository <http://github.com/olebole/python-cpl>`_. To access, do a::
+
+    git clone git://github.com/olebole/python-cpl.git
+
+  This gives you the current version in the subdirectory :file:`python-cpl`.
+  To update to the current version of an existing repository, do a 
+  ``git pull`` in the :file:`python-cpl` directory.
+
+  For more detailed information, check the manual page of :manpage:`git(1)` 
+  and the `github <http://github.com/olebole/python-cpl>`_ page of the project.
+
+Compilation
+-----------
+
+For compilation, a C compiler is needed additionally to the software mentioned
+above.
+
+The installation follows the standard procedure used in python. On default,
+the installation path :file:`/usr/local`. If using a non-standard installation
+path, add the directory :file:`{PREFIX}/lib/python2.7/site-packages/`
+(:file:`lib64/python2.7/site-packages/` on 64 bit systems) to your environment
+variable :envvar:`PYTHONPATH` where where :file:`{PREFIX}` is the installation
+path for the package.
+
+In the source directory of python-cpl, run
+
+.. code-block:: sh
+
+  python setup.py install --prefix=PREFIX
+
+There are other options available as well; use the :option:`--help` option to
+list them.
+
+Test suite
+----------
+
+There are a number of tests defined in :file:`test/TestRecipe.py`. To run
+them, you need first to compile the recipe in :file:`test/iiinstrumentp/`
+
+.. code-block:: sh
+
+  cd test/iiinstrumentp/
+  ./bootstrap
+  ./configure
+  make
+  cd ..
+  python TestRecipe.py
+
+Compiling the test recipe needs an installed CPL development environment.
+The tests may print a memory corruption detection by glibc. This is normal,
+since the tests also check this behaviour in the recipe.
+
+Tests are also automatically buils by `Travis CI <https://travis-ci.org/olebole/python-cpl>`_.
diff --git a/doc/msg.rst b/doc/msg.rst
new file mode 100644
index 0000000..0fc7aeb
--- /dev/null
+++ b/doc/msg.rst
@@ -0,0 +1,81 @@
+Log messages
+============
+
+We provide CPL log messages in two different ways: via Python logging and as
+a list of messages in the :class:`cpl.Result` object.
+
+For convienience, simple terminal messages and predefined log file output in a
+style similar to the original CPL messages.
+
+.. currentmodule:: cpl.logger
+
+Python style logging
+--------------------
+
+The preferred and most flexible way to do logging is the use of the
+:mod:`logging` module of Python. A basic setup (similar to the style used
+in `esorex <http://www.eso.org/sci/software/cpl/esorex.html>`_) is::
+
+  import logging
+
+  log = logging.getLogger()
+  log.setLevel(logging.INFO)
+  ch = logging.FileHandler('cpl_recipe.log')
+  ch.setLevel(logging.INFO)
+  fr = logging.Formatter('%(created)s [%(levelname)s] %(name)s: %(message)s',
+                         '%H:%M:%S')
+  ch.setFormatter(fr)
+  log.addHandler(ch)
+
+The default basic log name for CPL log messages in the recipes is
+:file:`cpl.{recipename}`. The log name can be changed with the ``logname``
+parameter of :class:`cpl.Recipe.__call__()` to follow own naming rules, or to
+separate the output of recipes that are executed in parallel::
+
+  res = [ muse_focus(f, logname = 'cpl.muse_focus%02i' % (i+1), threading = True) 
+          for i, f in enumerate(inputfiles) ]
+
+To the basic log name the function name is appended to allow selective logging
+of a certain function. The following sample line::
+
+  logging.getLogger('cpl.muse_sky.muse_sky_create_skymask').setLevel(logging.DEBUG)
+
+will log the debug messages from :func:`muse_sky_create_skymask()`
+additionally to the other messages.
+
+.. note::
+
+   Since the log messages are cached in CPL, they may occur with some
+   delay in the python log module. Also, log messages from different recipes
+   running in parallel may be mixed in their chronological order. The
+   resolution of the log time stamp is one second. The fields
+   :attr:`logging.LogRecord.args`, :attr:`logging.LogRecord.exc_info` and
+   :attr:`logging.LogRecord.lineno` are not set. Also, due to limitations in
+   the CPL logging module, level filtering is done only after the creation of
+   the log entries. This may cause performance problems if extensive debug
+   logging is done and filtered out by :class:`logging.Logger.setLevel()`. In
+   this case the :class:`cpl.Recipe.__call__()` parameter ``loglevel`` may be
+   used.
+
+.. seealso :: :data:`cpl.esorex.msg` and :data:`cpl.esorex.log`
+
+   EsoRex like convienience logging.
+
+Log message lists
+-----------------
+
+The :class:`cpl.Result` object as well as a :class:`cpl.CplError` have an
+attribute :attr:`cpl.Result.log` resp. :attr:`cpl.CplError.log` that contains
+the :class:`list` of all log messages.
+
+.. autoclass:: cpl.logger.LogList
+
+   .. autoattribute:: cpl.logger.LogList.error
+
+   .. autoattribute:: cpl.logger.LogList.warning
+
+   .. autoattribute:: cpl.logger.LogList.info
+
+   .. autoattribute:: cpl.logger.LogList.debug
+
+
diff --git a/doc/parallel.rst b/doc/parallel.rst
new file mode 100644
index 0000000..ed40e25
--- /dev/null
+++ b/doc/parallel.rst
@@ -0,0 +1,77 @@
+.. _parallel:
+
+Parallel execution
+==================
+
+The library allows a simple parallelization of recipe processing. The
+parallelization is done using independent processes and thus does not depend
+on parallelization features in the CPL or the recipe implementation.
+
+To specify that a recipe should be executed in the background, the
+:attr:`threaded` attribute needs to be set to :obj:`True`. This may be done
+either in the recipe constructor, as a recipe attribute or as a parameter of
+the execution call. Each of the following three recipes will start a
+background process for the BIAS calculation::
+
+  # Create a threaded recipe
+  r1 = cpl.Recipe('muse_bias', threaded = True)
+  result1 = r1([ 'bias1.fits', 'bias2.fits', 'bias3.fits'])
+
+  # Prepare a recipe for background execution
+  r2 = cpl.Recipe('muse_bias')
+  r2.threaded = True
+  result2 = r2([ 'bias1.fits', 'bias2.fits', 'bias3.fits'])
+
+  # Execute a recipe in background
+  r3 = cpl.Recipe('muse_bias')
+  result3 = r3([ 'bias1.fits', 'bias2.fits', 'bias3.fits'], threaded = True)
+
+If the :attr:`threaded` attribute is set to :obj:`True`, the execution call
+of the recipe immediately returns while the recipe is executed in the
+background. The current thread is stopped only if any of the results of the
+recipe is accessed and the recipe is still not finished.
+
+The result frame of a background recipe is a subclass of
+:class:`threading.Thread`. This interface may be used to control the thread
+execution.
+
+The simples way to use parallel processing is to create a list where the
+members are created by the execution of the recipe. The following example
+shows the parallel execution of the 'muse_focus' recipe::
+
+  muse_focus = cpl.Recipe('muse_focus', threaded = True)
+  muse_focus.calib.MASTER_BIAS = 'master_bias.fits'
+
+  # Create a list of input files
+  files = [ 'MUSE_CUNGC%02i.fits' % i for i in range(20, 30) ]
+
+  # Create a list of recipe results. Note that for each entry, a background
+  # process is started.
+  results = [ muse_focus(f) for f in files ]
+
+  # Save the results. The current thread is stopped until the according
+  # recipe is finished.
+  for i, res in enumerate(results):
+      res.FOCUS_TABLE.writeto('FOCUS_TABLE_%02i.fits' % (i+1))
+
+When using parallel processing note that the number of parallel processes is
+not limited by default, so this feature may produce a high load when called
+with a large number of processes. Parallelization in the recipe itself or in
+the CPL may also result in additional load.
+
+To limit the maximal number of parallel processes, the function
+:func:`cpl.Recipe.set_maxthreads()` can be called with the maximal number of
+parallel processes. Note that this function controls only the threads that are
+started afterwards.
+
+If the recipe execution fails, the according exception will be raised whenever
+one of the results is accessed.
+
+.. note ::
+
+   Recipes may contain an internal parallelization using the `openMP
+   <http://openmp.org>`_ interface. Although it is recommended to leave them
+   untouched, they may be changed via environment variable settungs in the
+   :attr:`cpl.Recipe.env` attribute. See
+   http://gcc.gnu.org/onlinedocs/libgomp/Environment-Variables.html for a list
+   of environment variables.
diff --git a/doc/param.rst b/doc/param.rst
new file mode 100644
index 0000000..09cbbba
--- /dev/null
+++ b/doc/param.rst
@@ -0,0 +1,8 @@
+.. module:: cpl
+
+The :class:`cpl.Parameter` class
+================================
+
+.. autoclass:: Parameter
+.. seealso:: :attr:`Recipe.param`
+
diff --git a/doc/recipe.rst b/doc/recipe.rst
new file mode 100644
index 0000000..82b1a3c
--- /dev/null
+++ b/doc/recipe.rst
@@ -0,0 +1,132 @@
+.. module:: cpl
+
+The Recipe interface
+====================
+
+.. autoclass:: Recipe
+
+Static members
+--------------
+
+.. autoattribute:: Recipe.path
+.. autoattribute:: Recipe.memory_mode
+.. automethod:: Recipe.list()
+.. automethod:: Recipe.set_maxthreads(n)
+
+Constructor
+-----------
+
+.. automethod:: Recipe.__init__
+
+Common attributes and methods
+-----------------------------
+
+These attributes and methods are available for all recipes.
+
+.. attribute:: Recipe.__name__
+
+   Recipe name.
+
+.. autoinstanceattribute:: Recipe.__file__
+
+   Shared library file name.
+
+.. autoattribute:: Recipe.__author__
+.. autoattribute:: Recipe.__email__
+.. autoattribute:: Recipe.__copyright__
+.. autoattribute:: Recipe.description
+.. autoattribute:: Recipe.version
+.. autoattribute:: Recipe.cpl_version
+.. autoattribute:: Recipe.cpl_description
+.. attribute:: Recipe.output_dir
+
+   Output directory if specified, or :obj:`None`. The recipe will write the
+   output files into this directory and return their file names. If the
+   directory does not exist, it will be created before the recipe is
+   executed. Output files within the output directory will be silently
+   overwritten. If no output directory is set, the recipe call will result in
+   :class:`astropy.io.fits.HDUList` result objects. The output directory may
+   be also set as parameter in the recipe call.
+
+.. attribute:: Recipe.temp_dir
+
+   Base directory for temporary directories where the recipe is executed. The
+   working dir is created as a subdir with a random file name. If set to
+   :obj:`None`, the system temp dir is used.  Defaults to :literal:`'.'`.
+
+.. attribute:: Recipe.threaded
+
+   Specify whether the recipe should be executed synchroniously or as
+   an extra process in the background.
+
+   .. seealso:: :ref:`parallel`
+
+.. autoattribute:: Recipe.tag
+.. autoattribute:: Recipe.tags
+.. autoattribute:: Recipe.output
+.. attribute:: Recipe.memory_dump
+
+   If set to 1, a memory dump is issued to stdout if the memory was
+   not totally freed after the execution. If set to 2, the dump is always
+   issued. Standard is 0: nothing dumped.
+
+Recipe parameters
+-----------------
+
+Recipe parameters may be set either via the :attr:`Recipe.param` attribute or
+as named keywords on the run execution. A value set in the recipe call will
+overwrite any value that was set previously in the :attr:`Recipe.param`
+attribute for that specific call.
+
+.. autoattribute:: Recipe.param
+.. seealso:: :class:`cpl.Parameter`
+
+Recipe frames
+-------------
+
+There are three groups of frames: calibration ("calib") frames, input ("raw")
+frames, and result ("product") frames.  Calibration frames may be set either
+via the :attr:`Recipe.calib` attribute or as named keywords on the run
+execution. A value set in the recipe call will overwrite any value that was
+set previously in the :attr:`Recipe.calib` attribute for that specific
+call. Input frames are always set in the recipe call. If their tag name was
+not given, the tag name from :attr:`Recipe.tag` is used if the recipe provides
+it.
+
+.. autoattribute:: Recipe.calib
+.. seealso:: :class:`cpl.FrameConfig`
+
+Runtime environment
+-------------------
+
+For debugging purposes, the runtime environment of the recipe may be
+changed. The change may be either done by specifying the :attr:`Recipe.env`
+attribute of as a parameter on the recipe invocation. The change will have no
+influence on the environment of the framework itself.
+
+.. note::
+
+   Some variables are only read on startup
+   (like :envvar:`MALLOC_CHECK_`), changing or deleting them will have
+   no effect.
+
+.. autoinstanceattribute:: Recipe.env
+
+   Environment changes for the recipe. This is a :class:`dict` with
+   the name of the environment variable as the key and the content as the
+   value.  It is possible to overwrite a specific environment
+   variable. Specifying :obj:`None` as value will remove the variable::
+
+     >>> muse_flat.env['MUSE_RESAMPLE_LAMBDA_LOG'] = '1'
+     >>> muse_flat.env['MUSE_TIMA_FILENAME'] = 'tima.fits'
+
+In a recipe call, the runtime environment may be overwritten as well::
+
+  >>> res = muse_flat( ..., env = {'MUSE_PLOT_TRACE':'true'})
+
+Recipe invocation
+-----------------
+
+.. automethod:: Recipe.__call__
+
+.. seealso:: :ref:`parallel`
diff --git a/doc/restrictions.rst b/doc/restrictions.rst
new file mode 100644
index 0000000..5fb708e
--- /dev/null
+++ b/doc/restrictions.rst
@@ -0,0 +1,51 @@
+.. _restrictions:
+
+.. module:: cpl
+
+Restrictions for CPL recipes
+============================
+
+Not every information can be retrieved from recipes with the standard CPL
+functions. Only MUSE recipes provide additional interfaces that allow the
+definition of input, calibration and output frames.
+
+All other interfaces will have the following restrictions:
+
+#. The :attr:`Recipe.calib` attribute is not filled with templates for
+   calibration frames. After recipe creation, this attribute is empty. Also, no
+   check on the required calibration frames may be done before calling the
+   recipe. Anything that is set here will be forwarded to the recipe.
+
+#. In the :mod:`cpl.esorex` support, directly assigning the recipe calibration
+   files from the SOF file with
+   :literal:`recipe.calib = cpl.esorex.read_sof('file')` will also put the
+   raw input file into :attr:`Recipe.calib` unless :attr:`Recipe.tags`
+   and/or :attr:`Recipe.tag` are set manually. The standard recipe
+   interface does not provide a way to distinguish between raw input and
+   calibration files.
+
+#. The :attr:`Recipe.tags` attribute is set to :obj:`None`.
+
+#. The :attr:`Recipe.tag` attribute is not initially set. If this
+   attribute is not set manually, the tag is required when executing the
+   attribute.
+
+#. Accessing the attribute :meth:`Recipe.output` raises an exception.
+
+Technical Background
+--------------------
+
+CPL recipes register all their parameter definitions with the CPL function
+:func:`cpl_parameterlist_append()`. All registered parameters may be retrieved
+from the recipe structure as a structure which contains all defined
+parameters.
+
+For frames, such a mechanism does not exist, although components of the
+infrastructure are implemented. The CPL modules :mod:`cpl_recipeconfig` allows
+the definition of input, raw, and output frames for a recipe. However, this
+module is only half-way done, has no connection to the recipe definition and
+is not mandantory for CPL recipes. The MUSE pipeline recipes (with the
+exception of those contributed by ESO) implement a central frameconfig
+registry which allows to access this meta information from the Python
+interface.
+
diff --git a/doc/result.rst b/doc/result.rst
new file mode 100644
index 0000000..c020870
--- /dev/null
+++ b/doc/result.rst
@@ -0,0 +1,145 @@
+.. module:: cpl
+
+Execution results
+=================
+
+Result frames
+-------------
+
+.. class:: cpl.Result
+
+   Calling :meth:`cpl.Recipe.__call__` returns an object that contains all
+   result ('production') frames in attributes. All results for one tag are
+   summarized in one attribute of the same name. So, the ``muse_bias`` recipe
+   returns a frame with the tag MASTER_BIAS in the according attribute::
+   
+     res = muse_bias(...)
+     res.MASTER_BIAS.writeto('master_bias')
+   
+   The attribute content is either a :class:`astropy.io.fits.HDUList` or a
+   :func:`list` of HDU lists, depending on the recipe and the call: If the
+   recipe produces one out put frame of a tag per input file, the attribute
+   contains a list if the recipe was called with a list, and if the recipe was
+   called with a single input frame, the result attribute will also contain a
+   single input frame. If the recipe combines all input frames to one output
+   frame, a single :class:`astropy.io.fits.HDUList` es returned, independent
+   of the input parameters. The following examples will illustrate this::
+   
+     muse_scibasic = cpl.Recipe('muse_scibasic')
+     ...
+     # Only single input frame, so we get one output frame
+     res = muse_scibasic('raw.fits')
+     res.PIXTABLE_OBJ.writeto('pixtable.fits')
+   
+     # List of input frames results in a list of output frames
+     res = muse_scibasic([ 'raw1.fits', 'raw2.fits', 'raw3.fits' ])
+     for i, h in res.PIXTABLE_OBJ:
+         h.writeto('pixtable%i.fits' % (i+1))
+   
+     # If we call the recipe with a list containing a single frame, we get a list
+     # with a single frame back
+     res = muse_scibasic([ 'raw1.fits' ])
+     res.PIXTABLE_OBJ[0].writeto('pixtable1.fits')
+   
+     # The bias recipe always returns one MASTER BIAS, regardless of number of
+     # input frames. So we always get a single frame back.
+     muse_bias = cpl.Recipe('muse_bias')
+     ...
+     res = muse_bias([ 'bias1.fits', 'bias2.fits', 'bias3.fits' ])
+     res.MASTER_BIAS.writeto('master_bias.fits')
+   
+   .. note:: This works well only for MUSE recipes. Other recipes dont provide
+      the necessary information about the recipe.
+
+Run statistics
+--------------   
+
+   In Addition to the result frames the :class:`cpl.Result` object provides the
+   attribute :attr:`cpl.Result.stat` which contains several statistics of the
+   recipe execution:
+   
+   .. attribute:: cpl.Result.return_code
+
+       The return code of the recipe. Since an exception is thrown if the 
+       return code indicates an error, this attribute is always set to 0.
+
+   .. attribute:: cpl.Result.stat.user_time
+   
+       CPU time in user mode, in seconds.
+   
+   .. attribute:: cpl.Result.stat.sys_time
+   
+       CPU time in system mode, in seconds.
+   
+   .. attribute:: cpl.Result.stat.memory_is_empty
+   
+       Flag whether the recipe terminated with freeing all available Memory.
+       This information is only available if the CPL internal memory
+       allocation functions are used. If this information is not available,
+       this flag ist set to :obj:`None`.
+
+       .. seealso:: :attr:`Recipe.memory_mode`
+
+Execution log
+-------------
+
+   .. attribute:: cpl.Result.log
+   
+       List of log messages for the recipe.
+
+       .. seealso:: :class:`cpl.logger.LogList`
+
+   .. attribute:: cpl.Result.error
+
+       If one or more error was set during the recipe run, the first error is
+       stored in this attribute. The following errors are chained and can be
+       accessed with the :attr:`cpl.CplError.next` attribute.  
+
+       .. note:: An error here does not indicate a failed recipe execution,
+          since a failed execution would result in a non-zero return code, and
+          an exception would be thrown.
+
+       .. seealso:: :class:`cpl.CplError`
+
+Thread control
+--------------
+
+   If the recipe was called in the background (see :ref:`parallel`), the result
+   object is returned immediately and is dervived from
+   :class:`threading.Thread`. Its interface can be used to control the thread
+   execution:
+   
+   .. method:: cpl.Result.isAlive()
+   
+      Returns whether the recipe is still running
+   
+   .. method:: cpl.Result.join(timeout = None)
+   
+      Wait until the recipe terminates. This blocks the calling thread until
+      the recipe terminates – either normally or through an unhandled
+      exception – or until the optional timeout occurs.
+   
+      When the timeout argument is present and not :obj:`None`, it should be
+      a floating point number specifying a timeout for the operation in
+      seconds (or fractions thereof). As :meth:`join` always returns
+      :obj:`None`, you must call :meth:`isAlive` after :meth:`join` to decide
+      whether a timeout happened – if the recipe is still running, the
+      :meth:`join` call timed out.
+
+   When the timeout argument is not present or :obj:`None`, the operation
+   will block until the recipe terminates.
+
+   A thread can be :meth:`cpl.Result.join` ed many times. 
+
+   Like in the foreground execution, the output frames may be retrieved as
+   attributes of the :class:`cpl.Result` frame. If any of the attributes is
+   accessed, the calling thread will block until the recipe is terminated. If
+   the recipe execution raised an exception, this exception will be raised
+   whenever an attribute is accessed.
+
+CPL Exceptions
+--------------
+.. autoexception:: CplError
+
+.. autoexception:: RecipeCrash
+
diff --git a/doc/tutorial.rst b/doc/tutorial.rst
new file mode 100644
index 0000000..2cc2b0f
--- /dev/null
+++ b/doc/tutorial.rst
@@ -0,0 +1,162 @@
+Tutorial
+========
+
+Simple example
+--------------
+
+The following code takes BIAS input file names from the command line and
+writes the MASTER BIAS to the file name provided with the :option:`-o`
+option::
+
+  from optparse import OptionParser
+  import sys
+  import cpl
+
+  parser = OptionParser(usage='%prog files')
+  parser.add_option('-o', '--output', help='Output file', default='master_bias.fits')
+  parser.add_option('-b', '--badpix-table', help='Bad pixel table')
+
+  (opt, filenames) = parser.parse_args()
+  if not filenames:
+      parser.print_help()
+      sys.exit()
+
+  cpl.esorex.init()
+
+  muse_bias = cpl.Recipe('muse_bias')
+  muse_bias.param.nifu = 1
+  muse_bias.calib.BADPIX_TABLE = opt.badpix_table
+
+  res = muse_bias(filenames)
+  res.MASTER_BIAS.writeto(opt.output)
+  
+Quick guide
+-----------
+
+Input lines are indicated with ">>>" (the python prompt).
+The package can be imported with
+
+>>> import cpl
+
+If you migrate from `Esorex <http://www.eso.org/sci/software/cpl/esorex.html>`_, you may just init the search path for CPL recipes
+from the esorex startup:
+
+>>> cpl.esorex.init()
+
+Otherwise, you will need to explicitely set the recipe search path:
+
+>>> cpl.Recipe.path = '/store/01/MUSE/recipes'
+
+List available recipes:
+
+>>> cpl.Recipe.list()
+[('muse_quick_image', ['0.2.0', '0.3.0']),
+ ('muse_scipost', ['0.2.0', '0.3.0']),
+ ('muse_scibasic', ['0.2.0', '0.3.0']),
+ ('muse_flat', ['0.2.0', '0.3.0']),
+ ('muse_subtract_sky', ['0.2.0', '0.3.0']),
+ ('muse_bias', ['0.2.0', '0.3.0']),
+ ('muse_ronbias', ['0.2.0', '0.3.0']),
+ ('muse_fluxcal', ['0.2.0', '0.3.0']),
+ ('muse_focus', ['0.2.0', '0.3.0']),
+ ('muse_lingain', ['0.2.0', '0.3.0']),
+ ('muse_dark', ['0.2.0', '0.3.0']),
+ ('muse_combine_pixtables', ['0.2.0', '0.3.0']),
+ ('muse_astrometry', ['0.2.0', '0.3.0']),
+ ('muse_wavecal', ['0.2.0', '0.3.0']),
+ ('muse_exp_combine', ['0.2.0', '0.3.0']),
+ ('muse_dar_correct', ['0.2.0', '0.3.0']),
+ ('muse_standard', ['0.2.0', '0.3.0']),
+ ('muse_create_sky', ['0.2.0', '0.3.0']),
+ ('muse_apply_astrometry', ['0.2.0', '0.3.0']),
+ ('muse_rebin', ['0.2.0', '0.3.0'])]
+
+Create a recipe specified by name:
+
+>>> muse_scibasic = cpl.Recipe('muse_scibasic')
+
+By default, it loads the recipe with the highest version number. You may also
+explicitely specify the version number:
+
+>>> muse_scibasic = cpl.Recipe('muse_scibasic', version = '0.2.0')
+
+List all parameters:
+
+>>> print muse_scibasic.param
+{'ybox': 40, 'passes': 2, 'resample': False, 'xbox': 15, 'dlambda': 1.25,
+ 'cr': 'none', 'thres': 5.8, 'nifu': 0, 'saveimage': True}
+
+Set a parameter:
+
+>>> muse_scibasic.param.nifu = 1
+
+Print the value of a parameter (:obj:`None` if the parameter is set to default)
+
+>>> print muse_scibasic.param.nifu.value
+1
+
+List all calibration frames:
+
+>>> print muse_scibasic.calib
+{'TRACE_TABLE': None, 'MASTER_SKYFLAT': None, 'WAVECAL_TABLE': None,
+ 'MASTER_BIAS': None, 'MASTER_DARK': None, 'GEOMETRY_TABLE': None,
+ 'BADPIX_TABLE': None, 'MASTER_FLAT': None, 'GAINRON_STAT': None}
+
+Set calibration frames with files:
+
+>>> muse_scibasic.calib.MASTER_BIAS    = 'MASTER_BIAS-01.fits'
+>>> muse_scibasic.calib.MASTER_FLAT    = 'MASTER_FLAT-01.fits'
+>>> muse_scibasic.calib.TRACE_TABLE    = 'TRACE_TABLE-01.fits'
+>>> muse_scibasic.calib.GEOMETRY_TABLE = 'geometry_table.fits'
+
+You may also set calibration frames with :class:`astropy.io.fits.HDUList`
+objects. This is especially useful if you want to change the file on the fly:
+
+>>> import astropy.io.fits
+>>> wavecal = astropy.io.fits.open('WAVECAL_TABLE-01_flat.fits')
+>>> wavecal[1].data.field('wlcc00')[:] *= 1.01
+>>> muse_scibasic.calib.WAVECAL_TABLE = wavecal
+
+To set more than one file for a tag, put the file names and/or
+:class:`astropy.io.fits.HDUList` objects into a list:
+
+>>> muse_scibasic.calib.MASTER_BIAS    = [ 'MASTER_BIAS-%02i.fits' % (i+1) 
+...                                        for i in range(24) ]
+
+To run the recipe, call it with the input file names as arguments. The product
+frames are returned in the return value of the call. If you don't specify an
+input frame tag, the default (first) one of the recipe is used.
+
+>>> res = muse_scibasic('Scene_fusion_1.fits')
+
+Run the recipe with a nondefault tag (use raw data tag as argument name):
+
+>>> res = muse_scibasic(raw = {'SKY':'sky_newmoon_no_noise_1.fits'})
+
+Parameters and calibration frames may be changed for a specific call by
+specifying them as arguments:
+
+>>> res =  muse_scibasic('Scene_fusion_1.fits', param = {'nifu': 2}, 
+...                      calib = {'MASTER_FLAT': None,
+...                               'WAVECAL_TABLE': 'WAVECAL_TABLE_noflat.fits'})
+
+The results of a calibration run are :class:`astropy.io.fits.HDUList` objects.
+To save them (use output tags as attributes):
+
+>>> res.PIXTABLE_OBJECT.writeto('Scene_fusion_pixtable.fits')
+
+They can also be used directly as input of other recipes. 
+
+>>> muse_sky = cpl.Recipe('muse_sky')
+...
+>>> res_sky = muse_sky(res.PIXTABLE_OBJECT)
+
+If not saved, the output is usually lost! During recipe run, a temporary
+directory is created where the :class:`astropy.io.fits.HDUList` input objects
+and the output files are put into. This directory is cleaned up afterwards.
+
+To control message verbosity on terminal (use :literal:`'debug'`,
+:literal:`'info'`, :literal:`'warn'`, :literal:`'error'` or :literal:`'off'`):
+
+>>> cpl.msg.esorex.level = 'debug'
+
diff --git a/setup.py b/setup.py
index dd69702..81aa865 100644
--- a/setup.py
+++ b/setup.py
@@ -5,14 +5,21 @@ from pkg_resources import require, DistributionNotFound
 author = 'Ole Streicher'
 email = 'python-cpl at liska.ath.cx'
 license_ = 'GPL'
-cpl_version = '0.6.2'
-with open('README') as readme:
-    description = readme.read().splitlines()
-    long_description = "\n".join(description[2:])
-    description = description[0]
+cpl_version = '0.7'
+description = "Python interface for the ESO Common Pipeline Library"
+long_description = '''This module can list, configure and execute CPL-based recipes from Python
+(python2 and python3).  The input, calibration and output data can be
+specified as FITS files or as ``astropy.io.fits`` objects in memory.
+
+The ESO `Common Pipeline Library <http://www.eso.org/sci/software/cpl/>`_
+(CPL) comprises a set of ISO-C libraries that provide a comprehensive,
+efficient and robust software toolkit. It forms a basis for the creation of
+automated astronomical data-reduction tasks. One of the features provided by
+the CPL is the ability to create data-reduction algorithms that run as plugins
+(dynamic libraries). These are called "recipes" and are one of the main
+aspects of the CPL data-reduction development environment.
+'''
 
-doc = '%s\n%s' % (description, 
-                  long_description[:long_description.find('Build instructions')])
 pkgname = 'python-cpl'
 baseurl = 'http://pypi.python.org/packages/source/%s/%s' % (pkgname[0], pkgname)
 classifiers = '''Development Status :: 4 - Beta
@@ -29,15 +36,16 @@ Topic :: Scientific/Engineering :: Astronomy
 
 
 def create_version_file(cpl_version = cpl_version):
-    vfile = open(os.path.join('cpl', 'version.py'), 'w')
-    vfile.write("version = %s\n" % repr(cpl_version))
-    vfile.write("author = %s\n" % repr(author))
-    vfile.write("email = %s\n" % repr(email))
-    vfile.write("license_ = %s\n" % repr(license_))
-    vfile.write("doc = %s\n" % repr(doc))
-    vfile.close()
+    with open(os.path.join('cpl', 'version.py'), 'w') as vfile:
+        vfile.write("version = %s\n" % repr(cpl_version))
+        vfile.write("author = %s\n" % repr(author))
+        vfile.write("email = %s\n" % repr(email))
+        vfile.write("license_ = %s\n" % repr(license_))
 
-create_version_file()
+try:
+    create_version_file()
+except IOError:
+    pass
 
 module1 = Extension('cpl.CPL_recipe',
                     sources = ['cpl/CPL_recipe.c', 'cpl/CPL_library.c'])
diff --git a/test/TestRecipe.py b/test/TestRecipe.py
new file mode 100644
index 0000000..35f1358
--- /dev/null
+++ b/test/TestRecipe.py
@@ -0,0 +1,931 @@
+import logging
+import os
+import shutil
+import tempfile
+import unittest
+
+import numpy
+try:
+    from astropy.io import fits
+except:
+    import pyfits as fits
+import cpl
+cpl.Recipe.memory_mode = 0
+
+recipe_name = 'rtest'
+raw_tag = 'RRRECIPE_DOCATG_RAW'
+
+class CplTestCase(unittest.TestCase):
+    def setUp(self):
+        cpl.Recipe.path = os.path.dirname(os.path.abspath(__file__))
+
+class RecipeTestCase(CplTestCase):
+    def setUp(self):
+        CplTestCase.setUp(self)
+        self.temp_dir = tempfile.mkdtemp()
+        self.recipe = cpl.Recipe(recipe_name)
+        self.recipe.temp_dir = self.temp_dir
+        self.recipe.tag = raw_tag
+        self.image_size = (16, 16)
+        self.raw_frame = fits.HDUList([
+                fits.PrimaryHDU(numpy.random.random_integers(0, 65000,
+                                                               self.image_size))])
+        self.raw_frame[0].header['HIERARCH ESO DET DIT'] = 0.0
+        self.raw_frame[0].header['HIERARCH ESO PRO CATG'] = raw_tag
+
+    def tearDown(self):
+        shutil.rmtree(self.temp_dir)
+
+class RecipeStatic(CplTestCase):
+    def test_list(self):
+        '''List available recipes'''
+        l = cpl.Recipe.list()
+        self.assertTrue(isinstance(l, list))
+        self.assertEqual(len(l), 1)
+        self.assertEqual(l[0], (recipe_name, ['0.0.1']))
+
+    def test_create_recipe(self):
+        '''Create a recipe specified by its name'''
+        recipe = cpl.Recipe(recipe_name)
+        self.assertTrue(isinstance(recipe, cpl.Recipe))
+
+    def test_create_recipe_version(self):
+        '''Create a recipe specified by its name and version'''
+        recipe = cpl.Recipe(recipe_name, version = '0.0.1')
+        self.assertTrue(isinstance(recipe, cpl.Recipe))        
+
+    def test_create_recipe_wrong_name(self):
+        '''Create a recipe specified by a wrong name'''
+        self.assertRaises(IOError, cpl.Recipe, 'wrongname')
+
+    def test_create_recipe_wrong_version(self):
+        '''Create a recipe specified by a wrong version'''
+        self.assertRaises(IOError, cpl.Recipe, recipe_name, version='0.0.10')
+
+    def test_create_recipe_filename(self):
+        '''Create a recipe specified by a the name and the filename'''
+        recipe = cpl.Recipe(recipe_name, filename = os.path.join(
+                os.path.dirname(os.path.abspath(__file__)),
+                'iiinstrumentp', 'recipes', '.libs', 'rtest.so'))
+        self.assertTrue(isinstance(recipe, cpl.Recipe))
+
+    def test_create_recipe_wrong_filename(self):
+        '''Create a recipe specified by a wrong filename'''
+        self.assertRaises(IOError, cpl.Recipe, recipe_name, 
+                          filename = os.path.join(
+                os.path.dirname(os.path.abspath(__file__)),
+                'iiinstrumentp', 'recipes', '.libs', 'rtest.o'))
+
+class RecipeCommon(RecipeTestCase):
+    def test_name(self):
+        '''Recipe name'''
+        self.assertEqual(self.recipe.__name__, recipe_name)
+
+    def test_author(self):
+        '''Author attribute'''
+        self.assertEqual(self.recipe.__author__, 'Ole Streicher')
+
+    def test_email(self):
+        '''Author attribute'''
+        self.assertEqual(self.recipe.__email__, 'python-cpl at liska.ath.cx')
+
+    def test_description(self):
+        '''Synopsis and description'''
+        self.assertTrue(isinstance(self.recipe.description[0], str))
+        self.assertTrue(len(self.recipe.description[0]) > 0)
+        self.assertTrue(isinstance(self.recipe.description[1], str))
+        self.assertTrue(len(self.recipe.description[1]) > 0)
+
+    def test_copyright(self):
+        '''Copyright'''
+        self.assertTrue(isinstance(self.recipe.__copyright__, str))
+        self.assertTrue(len(self.recipe.__copyright__) > 0)
+
+class RecipeParams(RecipeTestCase):
+    def test_str(self):
+        '''String parameter'''
+        self.assertTrue(isinstance(self.recipe.param.stropt, cpl.Parameter))
+        self.assertEqual(self.recipe.param.stropt.name, 'stropt')
+        self.assertEqual(self.recipe.param.stropt.context, 
+                         'iiinstrument.rtest')
+        self.assertEqual(self.recipe.param.stropt.default, None)
+        self.assertEqual(self.recipe.param.stropt.value, None)
+        self.assertEqual(self.recipe.param.stropt.range, None)
+        self.assertEqual(self.recipe.param.stropt.sequence, None)
+        self.recipe.param.stropt = 'more'
+        self.assertEqual(self.recipe.param.stropt.value, 'more')
+        del self.recipe.param.stropt 
+        self.assertEqual(self.recipe.param.stropt.value, None)
+
+    def test_bool(self):
+        '''Boolean parameter'''
+        self.assertTrue(isinstance(self.recipe.param.boolopt, cpl.Parameter))
+        self.assertEqual(self.recipe.param.boolopt.name, 'boolopt')
+        self.assertEqual(self.recipe.param.boolopt.default, True)
+        self.assertEqual(self.recipe.param.boolopt.value, None)
+        self.recipe.param.boolopt = False
+        self.assertEqual(self.recipe.param.boolopt.value, False)
+        del self.recipe.param.boolopt 
+        self.assertEqual(self.recipe.param.boolopt.value, None)
+        
+    def test_float(self):
+        '''Float parameter'''
+        self.assertTrue(isinstance(self.recipe.param.floatopt, cpl.Parameter))
+        self.assertEqual(self.recipe.param.floatopt.name, 'floatopt')
+        self.assertEqual(self.recipe.param.floatopt.default, 0.1)
+        self.assertEqual(self.recipe.param.floatopt.value, None)
+        self.recipe.param.floatopt = 1.1
+        self.assertEqual(self.recipe.param.floatopt.value, 1.1)
+        del self.recipe.param.floatopt 
+        self.assertEqual(self.recipe.param.floatopt.value, None)
+
+    def test_int(self):
+        '''Integer parameter'''
+        self.assertTrue(isinstance(self.recipe.param.intopt, cpl.Parameter))
+        self.assertEqual(self.recipe.param.intopt.name, 'intopt')
+        self.assertEqual(self.recipe.param.intopt.default, 2)
+        self.assertEqual(self.recipe.param.intopt.value, None)
+        self.recipe.param.intopt = -1
+        self.assertEqual(self.recipe.param.intopt.value, -1)
+        del self.recipe.param.intopt 
+        self.assertEqual(self.recipe.param.intopt.value, None)
+
+    def test_enum(self):
+        '''Enumeration (string) parameter'''
+        self.assertTrue(isinstance(self.recipe.param.enumopt, cpl.Parameter))
+        self.assertEqual(self.recipe.param.enumopt.name, 'enumopt')
+        self.assertEqual(self.recipe.param.enumopt.default, 'first')
+        self.assertEqual(self.recipe.param.enumopt.value, None)
+        self.recipe.param.enumopt = 'second'
+        self.assertEqual(self.recipe.param.enumopt.value, 'second')
+        del self.recipe.param.enumopt 
+        self.assertEqual(self.recipe.param.enumopt.value, None)
+        def setenumoptinvalid():
+            self.recipe.param.enumopt = 'invalid'
+        self.assertRaises(ValueError, setenumoptinvalid)
+
+    def test_range(self):
+        '''Range (float) parameter'''
+        self.assertTrue(isinstance(self.recipe.param.rangeopt, cpl.Parameter))
+        self.assertEqual(self.recipe.param.rangeopt.name, 'rangeopt')
+        self.assertEqual(self.recipe.param.rangeopt.default, 0.1)
+        self.assertEqual(self.recipe.param.rangeopt.value, None)
+        self.recipe.param.rangeopt = 0.4
+        self.assertEqual(self.recipe.param.rangeopt.value, 0.4)
+        del self.recipe.param.rangeopt 
+        self.assertEqual(self.recipe.param.rangeopt.value, None)
+        def setrangeoptinvalid():
+            self.recipe.param.rangeopt = 1.5
+        self.assertRaises(ValueError, setrangeoptinvalid)
+
+    def test_as_dict(self):
+        '''Use the parameter list as a dictionary'''
+        self.assertEqual(self.recipe.param.boolopt, 
+                         self.recipe.param['boolopt'])
+        self.assertEqual(self.recipe.param.boolopt, 
+                         self.recipe.param['iiinstrument.rtest.bool_option'])
+
+    def test_dotted_par(self):
+        '''Use a parameter that has a dot in its alias'''
+        self.assertEqual(self.recipe.param.dot.opt,
+                         self.recipe.param.dot['opt'])
+        self.assertEqual(self.recipe.param.dot.opt,
+                         self.recipe.param['dot.opt'])
+        self.assertEqual(self.recipe.param.dot.opt,
+                         self.recipe.param['iiinstrument.rtest.dotted.opt'])
+
+    def test_iterate(self):
+        '''Iteration over all parameters'''
+        for p in self.recipe.param:
+            self.assertTrue(isinstance(p, cpl.Parameter))
+        pars = [p.name for p in self.recipe.param]
+        self.assertEqual(len(pars), len(self.recipe.param))
+        self.assertTrue('stropt' in pars)
+        self.assertTrue('boolopt' in pars)
+
+    def test_set_dict(self):
+        '''Assign a dictionary to the parameter list'''
+        self.recipe.param = { 'stropt':'dmore', 'boolopt':True }
+        self.assertEqual(self.recipe.param.boolopt.value, True)
+        self.assertEqual(self.recipe.param.stropt.value, 'dmore')
+
+        # Check that we can assign a dictionary with the short names and string
+        self.recipe.param = { 'stropt':'dmore', 'boolopt':'False' }
+        self.assertEqual(self.recipe.param.boolopt.value, False)
+
+        # Check that we can assign a dictionary with the long names
+        self.recipe.param = { 'iiinstrument.rtest.string_option':'dless', 
+                      'iiinstrument.rtest.float_option':1.5, 
+                      'iiinstrument.rtest.bool_option':True }
+        self.assertEqual(self.recipe.param.stropt.value, 'dless')
+        self.assertEqual(self.recipe.param.floatopt.value, 1.5)
+        self.assertEqual(self.recipe.param.boolopt.value, True)
+
+    def test_delete(self):
+        '''Delete all parameter values to reset to default'''
+        self.recipe.param.boolopt.value = True
+        self.recipe.param.stropt.value = 'something'
+        del self.recipe.param
+        self.assertEqual(self.recipe.param.stropt.value, None)
+        self.assertEqual(self.recipe.param.boolopt.value, None)
+
+    def test_dir(self):
+        '''[TAB] completition. 
+        This requires to have   the __dir__() method working.
+        '''
+        self.assertEqual(set(self.recipe.param.__dir__()),
+                         set(p.name if '.' not in p.name
+                             else p.name.split('.', 1)[0]
+                             for p in self.recipe.param
+                             ))
+
+class RecipeCalib(RecipeTestCase):
+    def test_set(self):
+        '''Set a calibration frame'''
+        self.recipe.calib.FLAT = 'flat.fits'
+        self.assertEqual(self.recipe.calib.FLAT.frames, 'flat.fits')
+        
+    def test_set_dict(self):
+        '''Assign a dictionary to the calibration frame list'''
+        self.recipe.calib = { 'FLAT':'flat2.fits' }
+        self.assertEqual(self.recipe.calib.FLAT.frames, 'flat2.fits')
+
+    def test_del(self):
+        '''Delete a calibration frame set'''
+        self.recipe.calib.FLAT = 'flat.fits'
+        del self.recipe.calib.FLAT
+        f = self.recipe.calib.FLAT.frames
+        self.assertEqual(f, None)
+
+    def test_del_all(self):
+        '''Delete all calibration frame sets'''
+        self.recipe.calib.FLAT = 'flat.fits'
+        del self.recipe.calib
+        try:
+            f = self.recipe.calib.FLAT.frames
+        except:
+            f = None
+        self.assertEqual(f, None)
+
+    def test_dir(self):
+        '''[TAB] completition. 
+        This requires to have   the __dir__() method working.
+        '''
+        self.assertEqual(set(self.recipe.calib.__dir__()),
+                         set(f.tag for f in self.recipe.calib))
+
+class RecipeExec(RecipeTestCase):
+    def setUp(self):
+        RecipeTestCase.setUp(self)
+        self.flat_frame = fits.HDUList([
+                fits.PrimaryHDU(numpy.random.random_integers(0, 65000,
+                                                             self.image_size))])
+
+    def test_frames_keyword_dict(self):
+        '''Raw and calibration frames specified as keyword dict'''
+        self.recipe.tag = None
+        res = self.recipe(raw = {'RRRECIPE_DOCATG_RAW': self.raw_frame },
+                          calib = { 'FLAT':self.flat_frame })
+        self.assertTrue(isinstance(res, cpl.Result))
+        self.assertTrue(isinstance(res.THE_PRO_CATG_VALUE, fits.HDUList))
+        self.assertTrue(abs(self.raw_frame[0].data 
+                            - res.THE_PRO_CATG_VALUE[0].data).max() == 0)
+        try:
+            res.THE_PRO_CATG_VALUE.close()
+        except:
+            pass
+
+    def test_frames_keyword_calib(self):
+        '''Raw frame specified as keyword, calibration frame set in recipe'''
+        self.recipe.tag = None
+        self.recipe.calib.FLAT = self.flat_frame
+        res = self.recipe({'RRRECIPE_DOCATG_RAW':self.raw_frame})
+        self.assertTrue(isinstance(res, cpl.Result))
+        self.assertTrue(isinstance(res.THE_PRO_CATG_VALUE, fits.HDUList))
+        try:
+            res.THE_PRO_CATG_VALUE.close()
+        except:
+            pass
+
+    def test_frames_tag_keyword(self):
+        '''The 'tag' parameter'''
+        self.recipe.tag = None
+        self.recipe.calib.FLAT = self.flat_frame
+        res = self.recipe(self.raw_frame, tag = raw_tag)
+        self.assertTrue(isinstance(res, cpl.Result))
+        self.assertTrue(isinstance(res.THE_PRO_CATG_VALUE, fits.HDUList))
+        try:
+            res.THE_PRO_CATG_VALUE.close()
+        except:
+            pass
+
+    def test_frames_tag_attribute(self):
+        '''The 'tag' attribute'''
+        self.recipe.tag = raw_tag
+        res = self.recipe(self.raw_frame)
+        self.assertTrue(isinstance(res, cpl.Result))
+        self.assertTrue(isinstance(res.THE_PRO_CATG_VALUE, fits.HDUList))
+        try:
+            res.THE_PRO_CATG_VALUE.close()
+        except:
+            pass
+
+    def test_frames_one_element_input_list(self):
+        '''Use 1-element list as input'''
+        # --> we want a list back'''
+        res = self.recipe([self.raw_frame])
+        self.assertTrue(isinstance(res, cpl.Result))
+        self.assertFalse(isinstance(res.THE_PRO_CATG_VALUE, fits.HDUList))
+        self.assertTrue(isinstance(res.THE_PRO_CATG_VALUE, list))
+        try:
+            res.THE_PRO_CATG_VALUE[0].close()
+        except:
+            pass
+
+    def test_frames_many_element_input_list(self):
+        '''Use multiple files as input'''
+        # --> since we only get back one image, it is
+        # assumed to be a 'master', and we get back a plain frame'''
+        res = self.recipe([self.raw_frame, self.raw_frame])
+        self.assertTrue(isinstance(res, cpl.Result))
+        self.assertTrue(isinstance(res.THE_PRO_CATG_VALUE, fits.HDUList))
+        try:
+            res.THE_PRO_CATG_VALUE.close()
+        except:
+            pass
+
+    def test_output_dir_attribute(self):
+        '''Write an output dir specified as attribute'''
+        output_dir = os.path.join(self.temp_dir, 'out')
+        self.recipe.output_dir = output_dir
+        res = self.recipe(self.raw_frame)
+        self.assertTrue(isinstance(res, cpl.Result))
+        self.assertTrue(isinstance(res.THE_PRO_CATG_VALUE, str))
+        self.assertEqual(os.path.basename(res.THE_PRO_CATG_VALUE), 
+                         'rtest.fits')
+        self.assertTrue(os.path.isdir(output_dir))
+        self.assertTrue(os.path.isfile(res.THE_PRO_CATG_VALUE))
+        hdu = fits.open(res.THE_PRO_CATG_VALUE)
+        self.assertTrue(isinstance(hdu, fits.HDUList))
+        try:
+            hdu.close()
+        except:
+            pass
+
+    def test_output_dir_keyword(self):
+        '''Write an output dir specified as call keyword arg'''
+        output_dir = os.path.join(self.temp_dir, 'out')
+        res = self.recipe(self.raw_frame, output_dir = output_dir)
+        self.recipe.output_dir = output_dir
+        res = self.recipe(self.raw_frame)
+        self.assertTrue(os.path.isdir(output_dir))
+        self.assertTrue(isinstance(res, cpl.Result))
+        self.assertTrue(isinstance(res.THE_PRO_CATG_VALUE, str))
+        self.assertEqual(os.path.basename(res.THE_PRO_CATG_VALUE), 
+                         'rtest.fits')
+        self.assertTrue(os.path.isfile(res.THE_PRO_CATG_VALUE))
+        hdu = fits.open(res.THE_PRO_CATG_VALUE)
+        self.assertTrue(isinstance(hdu, fits.HDUList))
+        try:
+            hdu.close()
+        except:
+            pass
+
+    def test_param_default(self):
+        '''Test default parameter settings'''
+        res = self.recipe(self.raw_frame).THE_PRO_CATG_VALUE
+        self.assertEqual(res[0].header['HIERARCH ESO QC STROPT'].strip(),
+                         self.recipe.param.stropt.default or '')
+        self.assertEqual(res[0].header['HIERARCH ESO QC BOOLOPT'],
+                         self.recipe.param.boolopt.default)
+        self.assertEqual(res[0].header['HIERARCH ESO QC INTOPT'],
+                         self.recipe.param.intopt.default)
+        self.assertEqual(res[0].header['HIERARCH ESO QC FLOATOPT'],
+                         self.recipe.param.floatopt.default)
+        self.assertEqual(res[0].header['HIERARCH ESO QC ENUMOPT'],
+                         self.recipe.param.enumopt.default)
+        self.assertEqual(res[0].header['HIERARCH ESO QC RANGEOPT'],
+                         self.recipe.param.rangeopt.default)
+        try:
+            res.close()
+        except:
+            pass
+
+    def test_param_keyword_dict(self):
+        '''Parameter handling via keyword dict'''
+        res = self.recipe(self.raw_frame, 
+                          param = { 'stropt':'more' }).THE_PRO_CATG_VALUE
+        self.assertEqual(res[0].header['HIERARCH ESO QC STROPT'], 'more')
+        try:
+            res.close()
+        except:
+            pass
+
+    def test_param_keyword_dict_wrong(self):
+        '''Parameter handling via keyword dict'''
+        self.assertRaises(KeyError, self.recipe,
+                          self.raw_frame, param = { 'wrong':True })
+
+    def test_param_setting(self):
+        '''Parameter handling via recipe setting'''
+        self.recipe.param.stropt = 'more'
+        with self.recipe(self.raw_frame).THE_PRO_CATG_VALUE as res:
+            self.assertEqual(res[0].header['HIERARCH ESO QC STROPT'], 'more')
+
+    def test_param_delete(self):
+        '''Delete a parameter in a second run after setting it'''
+        self.recipe.param.intopt = 123
+        with self.recipe(self.raw_frame).THE_PRO_CATG_VALUE as res:
+            pass
+        del self.recipe.param.intopt
+        with self.recipe(self.raw_frame).THE_PRO_CATG_VALUE as res:
+            self.assertEqual(res[0].header['HIERARCH ESO QC INTOPT'], 2)
+
+    def test_param_overwrite(self):
+        '''Overwrite the recipe setting param via via keyword arg'''
+        self.recipe.param.stropt = 'more'
+        res = self.recipe(self.raw_frame, param = {'stropt':'less'}).THE_PRO_CATG_VALUE
+        self.assertEqual(res[0].header['HIERARCH ESO QC STROPT'], 'less')
+
+    def test_param_types(self):
+        '''Parameter types'''
+        self.recipe.param.stropt = 'more'
+        self.recipe.param.boolopt = False
+        self.recipe.param.intopt = 123
+        self.recipe.param.floatopt = -0.25
+        self.recipe.param.enumopt = 'third'
+        self.recipe.param.rangeopt = 0.125
+        with self.recipe(self.raw_frame).THE_PRO_CATG_VALUE as res:
+            self.assertEqual(res[0].header['HIERARCH ESO QC STROPT'], 'more')
+            self.assertEqual(res[0].header['HIERARCH ESO QC BOOLOPT'], False)
+            self.assertEqual(res[0].header['HIERARCH ESO QC INTOPT'], 123)
+            self.assertEqual(res[0].header['HIERARCH ESO QC FLOATOPT'], -0.25)
+            self.assertEqual(res[0].header['HIERARCH ESO QC ENUMOPT'], 'third')
+            self.assertEqual(res[0].header['HIERARCH ESO QC RANGEOPT'], 0.125)
+        
+    def test_disabled(self):
+        '''Parameter with CLI disabled'''
+        self.assertFalse(self.recipe.param.disabled.enabled[0])
+        self.assertTrue(self.recipe.param.intopt.enabled[0])
+#        self.recipe.param.disabled = 0.2
+#        res = self.recipe(self.raw_frame)
+#        self.assertEqual(res[0].header['HIERARCH ESO QC DISABLED'], 0.2)
+#        try:
+#            res.close()
+#        except:
+#            pass
+
+    def test_environment_setting(self):
+        '''Additional environment parameter via recipe setting'''
+        self.recipe.env['TESTENV'] = 'unkk'
+        with self.recipe(self.raw_frame).THE_PRO_CATG_VALUE as res:
+            self.assertEqual(res[0].header['HIERARCH ESO QC TESTENV'], 'unkk')
+
+    def test_environment_keyword(self):
+        '''Additional environment parameter via recipe call keyword'''
+        with self.recipe(self.raw_frame, 
+                         env = {'TESTENV':'kknu'}).THE_PRO_CATG_VALUE as res:
+            self.assertEqual(res[0].header['HIERARCH ESO QC TESTENV'], 'kknu')
+
+    def test_error(self):
+        '''Error handling'''
+        self.recipe.tag = 'some_unknown_tag'
+        self.assertRaises(cpl.CplError, self.recipe, self.raw_frame)
+
+    def test_parallel(self):
+        '''Parallel execution'''
+        results = list()
+        for i in range(20):
+            # mark each frame so that we can see their order
+            self.raw_frame[0].header['HIERARCH ESO RAW1 NR'] = i
+            results.append(self.recipe(self.raw_frame, param = {'intopt':i},
+                                       env = {'TESTENV':('knu%02i' % i)},
+                                       threaded = True))
+        for i, res in enumerate(results):
+            # check if we got the correct type
+            self.assertTrue(isinstance(res.THE_PRO_CATG_VALUE, fits.HDUList))
+            # check if we have the correct parameter
+            self.assertEqual(res.THE_PRO_CATG_VALUE[0].header[
+                    'HIERARCH ESO QC INTOPT'], i)
+            # check if we have the correct environment
+            self.assertEqual(res.THE_PRO_CATG_VALUE[0].header[
+                    'HIERARCH ESO QC TESTENV'], ('knu%02i' % i))
+            # check if we have the correct input frame
+            self.assertEqual(res.THE_PRO_CATG_VALUE[0].header[
+                    'HIERARCH ESO RAW1 NR'], i)
+            # check that the data were moved correctly
+            self.assertTrue(abs(self.raw_frame[0].data 
+                                - res.THE_PRO_CATG_VALUE[0].data).max() < 1e-6)
+            try:
+                res.THE_PRO_CATG_VALUE.close()
+            except:
+                pass
+
+    def test_error_parallel(self):
+        '''Error handling in parallel execution'''
+        self.recipe.tag = 'some_unknown_tag'
+        res = self.recipe(self.raw_frame, threaded = True)
+        def get(x):
+            return x.THE_PRO_CATG_VALUE
+        self.assertRaises(cpl.CplError, get, res)
+
+    def test_md5sum_result(self):
+        '''MD5sum of the result file'''
+        self.recipe.tag = raw_tag
+        res = self.recipe(self.raw_frame)
+        key = 'DATAMD5'
+        md5sum = res.THE_PRO_CATG_VALUE[0].header[key]
+        try:
+            res.THE_PRO_CATG_VALUE.close()
+        except:
+            pass
+        self.assertNotEqual(md5sum, 'Not computed')
+        self.assertEqual(len(md5sum), 
+                         len('9d123996fa9a7bda315d07e063043454'))
+
+    def test_md5sum_calib(self):
+        '''Created MD5sum for a HDUList calib file'''
+        self.recipe.tag = raw_tag
+        self.recipe.calib.FLAT = self.flat_frame
+        res = self.recipe(self.raw_frame)
+        key = 'HIERARCH ESO PRO REC1 CAL1 DATAMD5'
+        md5sum = res.THE_PRO_CATG_VALUE[0].header[key]
+        try:
+            res.THE_PRO_CATG_VALUE.close()
+        except:
+            pass
+        self.assertNotEqual(md5sum, 'Not computed')
+        self.assertEqual(len(md5sum), 
+                         len('9d123996fa9a7bda315d07e063043454'))
+
+class RecipeCrashing(RecipeTestCase):
+    def test_corrupted(self):
+        '''Handling of recipe crashes because of corrupted memory'''
+        self.recipe.param.crashing = 'free'
+        self.assertRaises(cpl.RecipeCrash, self.recipe, self.raw_frame)
+
+    def test_segfault(self):
+        '''Handling of recipe crashes because of segmentation fault'''
+        self.recipe.param.crashing = 'segfault'
+        self.assertRaises(cpl.RecipeCrash, self.recipe, self.raw_frame)
+
+    def test_cleanup_after_crash(self):
+        '''Test that a second run after a crash will succeed'''
+        output_dir = os.path.join(self.temp_dir, 'out')
+        self.recipe.output_dir = output_dir
+        self.recipe.param.crashing = 'segfault'
+        self.assertRaises(cpl.RecipeCrash, self.recipe, self.raw_frame)
+        del self.recipe.param.crashing
+        self.recipe(self.raw_frame)
+
+class RecipeRes(RecipeTestCase):
+    def setUp(self):
+        RecipeTestCase.setUp(self)
+        self.res = self.recipe(self.raw_frame)
+
+    def tearDown(self):
+        try:
+            res.THE_PRO_CATG_VALUE.close()
+        except:
+            pass
+
+    def test_attribute(self):
+        '''The result as an attribute'''
+        self.assertTrue(isinstance(self.res.THE_PRO_CATG_VALUE, 
+                                   fits.HDUList))
+
+    def test_dict(self):
+        '''The result as an attribute'''
+        self.assertTrue(isinstance(self.res['THE_PRO_CATG_VALUE'], 
+                                   fits.HDUList))
+
+    def test_len(self):
+        '''Length of the result'''
+        self.assertEqual(len(self.res), 1)
+
+    def test_iter(self):
+        '''Iterate over the result'''
+        for tag, hdu in self.res:
+            self.assertEqual(tag, 'THE_PRO_CATG_VALUE')
+            self.assertTrue(isinstance(hdu, fits.HDUList))
+
+class RecipeEsorex(CplTestCase):
+    def setUp(self):
+        CplTestCase.setUp(self)
+        self.temp_dir = tempfile.mkdtemp()
+
+    def tearDown(self):
+        CplTestCase.tearDown(self)
+        cpl.esorex.msg.level = cpl.esorex.msg.OFF
+        cpl.esorex.log.level = cpl.esorex.msg.OFF
+        shutil.rmtree(self.temp_dir)
+
+    def test_read_sof(self):
+        '''Read a SOF file'''
+        soffile = 'geometry_table1.fits GEOMETRY_TABLE\n' \
+            'geometry_table2.fits GEOMETRY_TABLE\n' \
+            'MASTER_BIAS-01.fits MASTER_BIAS\n' \
+            'MASTER_FLAT-01.fits MASTER_FLAT\n' \
+            '#sky_fullmoon_1.fits          SKY\n' \
+            'sky_fullmoon_2.fits          SKY\n'
+        self.assertEqual(cpl.esorex.load_sof(soffile),
+                         { 'GEOMETRY_TABLE': ['geometry_table1.fits',
+                                              'geometry_table2.fits' ],
+                           'MASTER_BIAS': 'MASTER_BIAS-01.fits',
+                           'MASTER_FLAT': 'MASTER_FLAT-01.fits',
+                           'SKY': 'sky_fullmoon_2.fits' })
+                           
+    def test_read_rc(self):
+        '''Read an EsoRec .rc file'''
+        rcfile = '# environment variable lambda_low.\n' \
+        'muse.muse_sky.lambda_low=4.65e+03\n' \
+        'muse.muse_sky.lambda_high=9.3e+03\n'
+        self.assertEqual(cpl.esorex.load_rc(rcfile), 
+                         { 'muse.muse_sky.lambda_low': '4.65e+03',
+                           'muse.muse_sky.lambda_high': '9.3e+03'})
+        
+    def test_esorex_init(self):
+        '''Init CPL from an esorex.rc file'''
+        rcfile = '''esorex.caller.recipe-dir=/some/dir
+        esorex.caller.msg-level=debug
+        esorex.caller.log-level=info
+        esorex.caller.log-dir=%s
+        esorex.caller.log-file=some.log''' % self.temp_dir
+        cpl.esorex.init(rcfile)
+        self.assertEqual(cpl.esorex.msg.level, cpl.esorex.msg.DEBUG)
+        self.assertEqual(cpl.esorex.log.level, cpl.esorex.msg.INFO)
+        self.assertEqual(cpl.esorex.log.dir, self.temp_dir)
+        self.assertEqual(cpl.esorex.log.filename, 'some.log')
+        self.assertEqual(cpl.Recipe.path, ['/some/dir'])
+
+    def test_esorex_log(self):
+        '''Write a logfile controlled by the convienence logger'''
+        dirname = os.path.join(self.temp_dir, 'log')
+        filename = 'python-cpl.log'
+        log_msg = 'Esorex convienence log'
+        os.mkdir(dirname)
+        cpl.esorex.log.dir = dirname
+        cpl.esorex.log.filename = filename
+        cpl.esorex.log.level = cpl.esorex.log.INFO
+        filename = os.path.join(dirname, filename)
+        logging.getLogger('cpl').info(log_msg)
+        self.assertTrue(os.path.exists(filename))
+        logfile = open(filename)
+        log_content = logfile.read()
+        logfile.close()
+        self.assertTrue(log_msg in log_content)
+        self.assertTrue('INFO' in log_content)
+
+    def test_esorex_log_off(self):
+        '''Switch the logfile off after writing something'''
+        dirname = os.path.join(self.temp_dir, 'log')
+        filename = 'python-cpl_off.log'
+        log_msg = 'Esorex convienence log'
+        os.mkdir(dirname)
+        cpl.esorex.log.dir = dirname
+        cpl.esorex.log.filename = 'python-cpl_debug.log'
+        cpl.esorex.log.level = 'debug'
+        logging.getLogger('cpl').debug(log_msg)
+        cpl.esorex.log.filename = filename
+        cpl.esorex.log.level = 'off'
+        logging.getLogger('cpl').debug(log_msg)
+        filename = os.path.join(dirname, filename)
+        logfile = open(filename)
+        log_content = logfile.read()
+        logfile.close()
+        self.assertEqual(len(log_content), 0)
+
+class RecipeLog(RecipeTestCase):
+    def setUp(self):
+        RecipeTestCase.setUp(self)
+        self.handler = RecipeLog.THandler()
+        logging.getLogger('cpl.rtest').addHandler(self.handler)
+        self.other_handler = RecipeLog.THandler()
+        logging.getLogger('othername').addHandler(self.other_handler)
+
+    def tearDown(self):
+        RecipeTestCase.tearDown(self)
+        logging.getLogger('cpl.rtest').removeHandler(self.handler)
+        logging.getLogger('othername').removeHandler(self.other_handler)
+
+    class THandler(logging.Handler):
+        def __init__(self):
+            logging.Handler.__init__(self)
+            self.logs = list()
+
+        def emit(self, record):
+            self.logs.append(record)
+
+        def clear(self):
+            self.logs = list()
+
+    def test_logging_DEBUG(self):
+        '''Injection of CPL messages into the python logging system'''
+        self.handler.clear()
+        logging.getLogger().setLevel(logging.DEBUG)
+        res = self.recipe(self.raw_frame)
+        try:
+            res.THE_PRO_CATG_VALUE.close()
+        except:
+            pass
+
+        # check that the logs are not empty
+        self.assertNotEqual(len(self.handler.logs), 0)
+        funcnames = set()
+        lognames = set()
+        for r in self.handler.logs:
+            # Check that we saved the right class
+            self.assertTrue(isinstance(r, logging.LogRecord))
+            # Check that a message was provided
+            self.assertNotEqual(r.msg, None)
+            # Check that a function name was provided
+            self.assertNotEqual(r.funcName, None)
+            funcnames.add(r.funcName)
+            lognames.add(r.name)
+        # Check that we had at least one expected entry
+        self.assertTrue('cpl_dfs_product_save' in funcnames)
+        self.assertTrue('cpl.rtest.cpl_dfs_product_save' in lognames)
+        
+    def test_logging_INFO(self):
+        '''Filtering INFO messages'''
+        self.handler.clear()
+        logging.getLogger('cpl.rtest').setLevel(logging.INFO)
+        res = self.recipe(self.raw_frame)
+        try:
+            res.THE_PRO_CATG_VALUE.close()
+        except:
+            pass
+
+        # check that the logs are not empty
+        self.assertNotEqual(len(self.handler.logs), 0)
+
+    def test_logging_WARN(self):
+        '''Filtering WARN messages'''
+        self.handler.clear()
+        logging.getLogger('cpl.rtest').setLevel(logging.WARN)
+        res = self.recipe(self.raw_frame)
+        try:
+            res.THE_PRO_CATG_VALUE.close()
+        except:
+            pass
+
+        # check that the logs are not empty
+        self.assertNotEqual(len(self.handler.logs), 0)
+
+    def test_logging_ERROR(self):
+        '''Filtering of error messages'''
+        # There is no error msg written by the recipe, so it should be empty.
+        self.handler.clear()
+        logging.getLogger('cpl.rtest').setLevel(logging.ERROR)
+        res = self.recipe(self.raw_frame)
+        try:
+            res.THE_PRO_CATG_VALUE.close()
+        except:
+            pass
+        self.assertEqual(len(self.handler.logs), 0)
+
+    def test_logging_common(self):
+        '''Log name specification on recipe call'''
+        self.handler.clear()
+        self.other_handler.clear()
+        res = self.recipe(self.raw_frame, logname = 'othername')
+        try:
+            res.THE_PRO_CATG_VALUE.close()
+        except:
+            pass
+        self.assertNotEqual(len(self.other_handler.logs), 0)
+
+    def test_logging_multiline(self):
+        '''Multiple lines in messages'''
+        self.handler.clear()
+        logging.getLogger('cpl.rtest').setLevel(logging.INFO)
+        res = self.recipe(self.raw_frame)
+        try:
+            res.THE_PRO_CATG_VALUE.close()
+        except:
+            pass
+        # check that the multi line log sequence appears
+        multiline = 0
+        tag = 'multiline#'
+        for l in self.handler.logs:
+            if tag not in l.msg:
+                continue
+            i = int(l.msg[l.msg.index(tag)+len(tag):].split()[0])
+            self.assertEqual(multiline + 1, i)
+            multiline = i
+        self.assertEqual(multiline, 3)
+
+    def test_result(self):
+        '''"log" attribute of the result object'''
+        res = self.recipe(self.raw_frame)
+        # Check that we get a not-empty list back
+        self.assertTrue(isinstance(res.log, list))
+        self.assertNotEqual(len(res.log), 0)
+        self.assertTrue(isinstance(res.log[0], logging.LogRecord))
+
+        # Check that we can read debug messages
+        self.assertNotEqual(len(res.log.debug), 0)
+        self.assertTrue(isinstance(res.log.debug[0], str))
+        # Check that we can read info messages
+        self.assertNotEqual(len(res.log.info), 0)
+        self.assertTrue(isinstance(res.log.info[0], str))
+        # Check that we can read warning messages
+        self.assertNotEqual(len(res.log.warning), 0)
+        self.assertTrue(isinstance(res.log.warning[0], str))
+        # Check that there were no error messages
+        self.assertEqual(len(res.log.error), 0)
+        try:
+            res.THE_PRO_CATG_VALUE.close()
+        except:
+            pass
+
+    def test_error(self):
+        '''"log" attribute of the CplError object'''
+        try:
+            self.recipe('test.fits')
+        except cpl.CplError as r:
+            res = r
+        # Check that we get a not-empty list back
+        self.assertTrue(isinstance(res.log, list))
+        self.assertNotEqual(len(res.log), 0)
+        self.assertTrue(isinstance(res.log[0], logging.LogRecord))
+        # Check that we can read debug messages
+        self.assertNotEqual(len(res.log.debug), 0)
+        self.assertTrue(isinstance(res.log.debug[0], str))
+        # Check that we can read info messages
+        self.assertNotEqual(len(res.log.info), 0)
+        self.assertTrue(isinstance(res.log.info[0], str))
+        # Check that we can read warning messages
+        self.assertNotEqual(len(res.log.warning), 0)
+        self.assertTrue(isinstance(res.log.warning[0], str))
+        # Check that we can read error messages
+        self.assertNotEqual(len(res.log.error), 0)
+        self.assertTrue(isinstance(res.log.error[0], str))
+
+class ProcessingInfo(RecipeTestCase):
+    def setUp(self):
+        RecipeTestCase.setUp(self)
+        '''Parameter storage in the result'''
+        self.recipe.param.stropt = 'more'
+        self.recipe.param.boolopt = False
+        self.recipe.param.intopt = 123
+        self.recipe.param.floatopt = -0.25
+        self.recipe.param.enumopt = 'third'
+        self.recipe.param.rangeopt = 0.125
+        self.recipe.calib.FLAT = fits.HDUList([
+                fits.PrimaryHDU(numpy.random.random_integers(0, 65000,
+                                                          self.image_size))])
+        self.res = self.recipe(self.raw_frame).THE_PRO_CATG_VALUE
+        self.pinfo = cpl.dfs.ProcessingInfo(self.res)
+
+    def tearDown(self):
+        try:
+            self.res.close()
+        except:
+            pass
+
+    def test_list(self):
+        '''All processing infos as a list'''
+        pi = cpl.dfs.ProcessingInfo.list(self.res[0])
+        self.assertTrue(len(pi), 1)
+        self.assertTrue(pi[0], self.pinfo)
+
+    def test_param(self):
+        '''Parameter information'''
+        self.assertEqual(len(self.pinfo.param), len(self.recipe.param))
+        for p in self.recipe.param:
+            self.assertEqual(self.pinfo.param[p.name], 
+                             p.value if p.value is not None else p.default)
+
+    def test_calib(self):
+        '''Calibration frame information'''
+        self.assertEqual(len(self.pinfo.calib), 1)
+        self.assertEqual(self.pinfo.calib['FLAT'][-5:], '.fits')
+
+    def test_tag(self):
+        '''Input tag information'''
+        self.assertEqual(self.pinfo.tag, self.recipe.tag)
+
+    def test_raw(self):
+        '''Raw file information'''
+        self.assertEqual(self.pinfo.raw[-5:], '.fits')
+
+    def test_name(self):
+        '''Recipe and pipeline name information'''
+        self.assertEqual(self.pinfo.name, self.recipe.__name__)
+        self.assertEqual(self.pinfo.pipeline, 'iiinstrument')
+
+    def test_version(self):
+        '''Version information'''
+        self.assertEqual(self.pinfo.version[0], self.recipe.version[0])
+        self.assertEqual(self.pinfo.cpl_version, 'cpl-%s' % self.recipe.cpl_version)
+
+    def test_md5(self):
+        '''MD5 checksums'''
+        md5sum = self.res[0].header.get('DATAMD5')
+        self.assertEqual(md5sum, self.pinfo.md5sum)
+        md5sum = self.res[0].header.get('HIERARCH ESO PRO REC1 CAL1 DATAMD5')
+        self.assertEqual(md5sum, self.pinfo.md5sums[self.pinfo.calib['FLAT']])
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/test/iiinstrumentp/.gitignore b/test/iiinstrumentp/.gitignore
new file mode 100644
index 0000000..7e0ce2c
--- /dev/null
+++ b/test/iiinstrumentp/.gitignore
@@ -0,0 +1,31 @@
+Makefile
+Makefile.in
+Makefile.purify
+aclocal.m4
+admin/compile
+autom4te.cache/
+config.h
+config.h.in
+config.log
+config.status
+configure
+doxygen/Doxyfile
+iiinstrument/Makefile
+iiinstrument/Makefile.in
+iiinstrument/tests/Makefile
+iiinstrument/tests/Makefile.in
+libtool
+recipes/Makefile
+recipes/Makefile.in
+stamp-h1
+*.lo
+*.la
+.deps
+.libs
+admin
+m4macros/libtool.m4
+m4macros/ltoptions.m4
+m4macros/ltsugar.m4
+m4macros/ltversion.m4
+m4macros/lt~obsolete.m4
+
diff --git a/test/iiinstrumentp/COPYING b/test/iiinstrumentp/COPYING
new file mode 100644
index 0000000..d60c31a
--- /dev/null
+++ b/test/iiinstrumentp/COPYING
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+

+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+

+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+

+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+

+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+

+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year  name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/test/iiinstrumentp/Makefile.am b/test/iiinstrumentp/Makefile.am
new file mode 100644
index 0000000..0055bd9
--- /dev/null
+++ b/test/iiinstrumentp/Makefile.am
@@ -0,0 +1,32 @@
+## Process this file with automake to produce Makefile.in
+
+##   This file is part of the iiinstrument Pipeline
+##   Copyright (C) 2002,2003 European Southern Observatory
+
+##   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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+AUTOMAKE_OPTIONS = 1.8 foreign
+
+ACLOCAL_AMFLAGS = -I m4macros
+
+DISTCLEANFILES = *~
+
+SUBDIRS = iiinstrument recipes
+
+EXTRA_DIST = m4macros/eso.m4 m4macros/cpl.m4
+
+libtool: $(LIBTOOL_DEPS)
+	$(SHELL) ./config.status --recheck
+
diff --git a/test/iiinstrumentp/acinclude.m4 b/test/iiinstrumentp/acinclude.m4
new file mode 100644
index 0000000..7eca00c
--- /dev/null
+++ b/test/iiinstrumentp/acinclude.m4
@@ -0,0 +1,154 @@
+# IIINSTRUMENT_SET_PREFIX(PREFIX)
+#---------------------------
+AC_DEFUN([IIINSTRUMENT_SET_PREFIX],
+[
+    unset CDPATH
+    # make $PIPE_HOME the default for the installation
+    AC_PREFIX_DEFAULT($1)
+
+    if test "x$prefix" = "xNONE"; then
+        prefix=$ac_default_prefix
+        ac_configure_args="$ac_configure_args --prefix $prefix"
+    fi
+
+    if test "x$exec_prefix" = "xNONE"; then
+        exec_prefix=$prefix
+    fi
+
+])
+
+
+# IIINSTRUMENT_SET_VERSION_INFO(VERSION, [CURRENT], [REVISION], [AGE])
+#----------------------------------------------------------------
+# Setup various version information, especially the libtool versioning
+AC_DEFUN([IIINSTRUMENT_SET_VERSION_INFO],
+[
+    iiinstrument_version=`echo "$1" | sed -e 's/[[a-z,A-Z]].*$//'`
+
+    iiinstrument_major_version=`echo "$iiinstrument_version" | \
+        sed 's/\([[0-9]]*\).\(.*\)/\1/'`
+    iiinstrument_minor_version=`echo "$iiinstrument_version" | \
+        sed 's/\([[0-9]]*\).\([[0-9]]*\)\(.*\)/\2/'`
+    iiinstrument_micro_version=`echo "$iiinstrument_version" | \
+        sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'`
+
+    if test -z "$iiinstrument_major_version"; then iiinstrument_major_version=0
+    fi
+
+    if test -z "$iiinstrument_minor_version"; then iiinstrument_minor_version=0
+    fi
+
+    if test -z "$iiinstrument_micro_version"; then iiinstrument_micro_version=0
+    fi
+
+    IIINSTRUMENT_VERSION="$iiinstrument_version"
+    IIINSTRUMENT_MAJOR_VERSION=$iiinstrument_major_version
+    IIINSTRUMENT_MINOR_VERSION=$iiinstrument_minor_version
+    IIINSTRUMENT_MICRO_VERSION=$iiinstrument_micro_version
+
+    if test -z "$4"; then IIINSTRUMENT_INTERFACE_AGE=0
+    else IIINSTRUMENT_INTERFACE_AGE="$4"
+    fi
+
+    IIINSTRUMENT_BINARY_AGE=`expr 100 '*' $IIINSTRUMENT_MINOR_VERSION + $IIINSTRUMENT_MICRO_VERSION`
+    IIINSTRUMENT_BINARY_VERSION=`expr 10000 '*' $IIINSTRUMENT_MAJOR_VERSION + \
+                          $IIINSTRUMENT_BINARY_AGE`
+
+    AC_SUBST(IIINSTRUMENT_VERSION)
+    AC_SUBST(IIINSTRUMENT_MAJOR_VERSION)
+    AC_SUBST(IIINSTRUMENT_MINOR_VERSION)
+    AC_SUBST(IIINSTRUMENT_MICRO_VERSION)
+    AC_SUBST(IIINSTRUMENT_INTERFACE_AGE)
+    AC_SUBST(IIINSTRUMENT_BINARY_VERSION)
+    AC_SUBST(IIINSTRUMENT_BINARY_AGE)
+
+    AC_DEFINE_UNQUOTED(IIINSTRUMENT_MAJOR_VERSION, $IIINSTRUMENT_MAJOR_VERSION,
+                       [IIINSTRUMENT major version number])
+    AC_DEFINE_UNQUOTED(IIINSTRUMENT_MINOR_VERSION, $IIINSTRUMENT_MINOR_VERSION,
+                       [IIINSTRUMENT minor version number])
+    AC_DEFINE_UNQUOTED(IIINSTRUMENT_MICRO_VERSION, $IIINSTRUMENT_MICRO_VERSION,
+                       [IIINSTRUMENT micro version number])
+    AC_DEFINE_UNQUOTED(IIINSTRUMENT_INTERFACE_AGE, $IIINSTRUMENT_INTERFACE_AGE,
+                       [IIINSTRUMENT interface age])
+    AC_DEFINE_UNQUOTED(IIINSTRUMENT_BINARY_VERSION, $IIINSTRUMENT_BINARY_VERSION,
+                       [IIINSTRUMENT binary version number])
+    AC_DEFINE_UNQUOTED(IIINSTRUMENT_BINARY_AGE, $IIINSTRUMENT_BINARY_AGE,
+                       [IIINSTRUMENT binary age])
+
+    ESO_SET_LIBRARY_VERSION([$2], [$3], [$4])
+])
+
+
+# IIINSTRUMENT_SET_PATHS
+#------------------
+# Define auxiliary directories of the installed directory tree.
+AC_DEFUN([IIINSTRUMENT_SET_PATHS],
+[
+
+    if test -z "$plugindir"; then
+        plugindir='${libdir}/${PACKAGE}/plugins/${PACKAGE}-${VERSION}'
+    fi
+
+    if test -z "$htmldir"; then
+        htmldir='${datadir}/doc/${PACKAGE}/html'
+    fi
+
+    if test -z "$configdir"; then
+       configdir='${datadir}/${PACKAGE}/config'
+    fi
+
+    AC_SUBST(plugindir)
+    AC_SUBST(htmldir)
+    AC_SUBST(configdir)
+
+
+    # Define a preprocesor symbol for the plugin search paths
+
+    AC_DEFINE_UNQUOTED(IIINSTRUMENT_PLUGIN_DIR, "${PACKAGE}/plugins",
+                       [Plugin directory tree prefix])
+
+    eval plugin_dir="$plugindir"
+    plugin_path=`eval echo $plugin_dir | \
+                sed -e "s/\/${PACKAGE}-${VERSION}.*$//"`
+
+    AC_DEFINE_UNQUOTED(IIINSTRUMENT_PLUGIN_PATH, "$plugin_path",
+                       [Absolute path to the plugin directory tree])
+
+])
+
+
+# IIINSTRUMENT_CREATE_SYMBOLS
+#-----------------------
+# Define include and library related makefile symbols
+AC_DEFUN([IIINSTRUMENT_CREATE_SYMBOLS],
+[
+
+    # Symbols for package include file and library search paths
+
+    IIINSTRUMENT_INCLUDES='-I$(top_srcdir)/iiinstrument'
+    IIINSTRUMENT_LDFLAGS='-L$(top_builddir)/iiinstrument'
+
+    all_includes='$(IIINSTRUMENT_INCLUDES) $(CPL_INCLUDES) $(EXTRA_INCLUDES)'
+    all_ldflags='$(IIINSTRUMENT_LDFLAGS) $(CPL_LDFLAGS) $(EXTRA_LDFLAGS)'
+
+    # Library aliases
+
+    LIBIIINSTRUMENT='$(top_builddir)/iiinstrument/libiiinstrument.la'
+
+    # Substitute the defined symbols
+
+    AC_SUBST(IIINSTRUMENT_INCLUDES)
+    AC_SUBST(IIINSTRUMENT_LDFLAGS)
+
+    AC_SUBST(LIBIIINSTRUMENT)
+
+    # Check for CPL and user defined libraries
+    AC_REQUIRE([CPL_CHECK_LIBS])
+    AC_REQUIRE([ESO_CHECK_EXTRA_LIBS])
+
+    all_includes='$(IIINSTRUMENT_INCLUDES) $(CPL_INCLUDES) $(EXTRA_INCLUDES)'
+    all_ldflags='$(IIINSTRUMENT_LDFLAGS) $(CPL_LDFLAGS) $(EXTRA_LDFLAGS)'
+
+    AC_SUBST(all_includes)
+    AC_SUBST(all_ldflags)
+])
diff --git a/test/iiinstrumentp/bootstrap b/test/iiinstrumentp/bootstrap
new file mode 100755
index 0000000..b54ae02
--- /dev/null
+++ b/test/iiinstrumentp/bootstrap
@@ -0,0 +1,546 @@
+#! /bin/bash
+#
+# This script is only for the CVS repository to bootstrap the checked
+# out module. It will be deleted before making the distribution.
+#
+
+#set -x
+
+# Helper functions
+
+bt_usage ()
+{
+
+    echo "Usage: $__bt_script $__bt_usage"
+
+}
+
+
+bt_message ()
+{
+
+    echo "$1"
+
+}
+
+
+bt_info ()
+{
+
+    if test $__bt_level -eq 0; then
+        echo "$__bt_script: $1"
+    else
+        echo "$__bt_script[$__bt_level]: $1"
+    fi
+
+}
+
+
+bt_warning ()
+{
+
+    if test $__bt_level -eq 0; then
+        echo "$__bt_script: Warning: $1"
+    else
+        echo "$__bt_script[$__bt_level]: Warning: $1"
+    fi
+
+}
+
+
+bt_error ()
+{
+
+    if test $__bt_level -eq 0; then
+        echo "$__bt_script: Error: $1"
+    else
+        echo "$__bt_script[$__bt_level]: Error: $1"
+    fi
+
+}
+
+
+bt_init ()
+{
+    __bt_script=`basename $0`
+    readonly __bt_script
+
+    __bt_usage="[-hlcCD] [-L level] [-d helpers-dir] [-s subdirs-file]"
+    bt_options="hlcCDL:d:s:"
+    readonly __bt_usage bt_options
+
+    # Recursion level
+    __bt_level=0
+
+    # Default helpers directory
+    bt_auxdir="admin"
+
+    # Default subdirs file
+    bt_subdirs_file="bootdirs"
+
+    # Disable check for maintainer tools
+    bt_check_tools=1
+
+    # Distribution mode (default is to run in maintainer mode)
+    bt_dist_mode=no
+
+    # Local flag (default is to work recursive)
+    bt_run_local=0
+
+    # Copy files flag
+    bt_copy_files="--copy"
+
+    # Process command line
+    set -- `getopt $bt_options $*`
+
+    if test $? != 0; then
+	bt_usage
+	exit 1
+    fi
+
+    for opt in $*; do
+	case $opt in
+	-h) bt_usage
+	    exit 0
+	    ;;
+
+	-l) bt_run_local=1
+	    shift
+	    ;;
+
+	-c) bt_copy_files=""
+	    shift
+	    ;;
+
+	-C) bt_check_tools=0
+	    shift
+	    ;;
+
+	-D) bt_dist_mode=yes
+	    shift
+	    ;;
+
+	-L) __bt_level=$2
+	    shift 2
+	    ;;
+
+	-d) bt_auxdir=$2
+	    shift 2
+	    ;;
+
+	-s) bt_subdirs_file=$2
+	    shift 2
+	    ;;
+
+	--) shift
+	    break
+	    ;;
+	esac
+    done
+
+
+    # Check existance of the helpers directory
+    if test ! -d $bt_auxdir; then
+	mkdir -p $bt_auxdir
+    fi
+
+
+    # Force local mode if subdirs file is missing
+    if test ! -r $bt_subdirs_file; then
+	bt_run_local=1
+    fi
+
+    return 0
+
+}
+
+
+bt_check_autoconf_template ()
+{
+    for f in configure.ac configure.in; do
+	if test -r $f; then
+	    bt_configure=$f
+	    break
+	fi
+    done
+
+    if test -z "$bt_configure"; then
+	bt_error "No GNU autoconf configure template found!"
+	return 1
+    fi
+
+    return 0
+}
+
+
+bt_needs_automake ()
+{
+    bt_use_am=no
+
+    if egrep "^AM_INIT_AUTOMAKE" $bt_configure >/dev/null 2>&1; then
+	if test ! -r Makefile.am; then
+	    bt_error "No GNU automake Makefile template found!"
+	    return 1
+	fi
+
+        bt_use_am=yes
+    fi
+
+    return 0
+}
+
+
+bt_needs_libtool ()
+{
+    bt_use_lt=no
+
+    if egrep "^AC_PROG_LIBTOOL" $bt_configure >/dev/null 2>&1; then
+        bt_use_lt=yes
+    fi
+
+    return 0
+}
+
+
+bt_check_build_tools ()
+{
+
+    bt_info "Checking for maintainer tools..."
+
+    # Temporarily remove the current working directory from PATH
+    save_PATH="$PATH"
+    PATH="`echo $PATH | sed -e 's/^\.://' -e 's/:\.:/:/g' -e 's/:\.$//'`"
+
+    # Autoconf 2.59 or newer
+    bt_autoconf=`which autoconf 2>/dev/null | grep '^/' | head -1`
+    bt_ac_version=`$bt_autoconf --version 2>/dev/null | head -1 \
+    	      | sed -e 's/^[^0-9]*//' -e 's/[a-z]* *$//'`
+    
+    if test -z "$bt_ac_version"; then
+        bt_error "GNU autoconf not found in your PATH!"
+        bt_error "You need autoconf 2.59 or newer installed."
+    
+        return 1
+    else
+        save_IFS="$IFS"
+        IFS=.
+        set $bt_ac_version
+        IFS="$save_IFS"
+    
+        if test "$1" -lt 2 || test x"$1" = x2 && test "$2" -lt 59; then
+            bt_error "Installed GNU autoconf $bt_ac_version is too old!"
+            bt_error "You need autoconf 2.59 or newer installed."
+    
+            return 1
+        else
+            bt_info "GNU autoconf $bt_ac_version found."
+            bt_ac_path=`dirname $bt_autoconf`
+            bt_use_ac=yes
+        fi
+    fi
+
+
+    # Automake 1.8 or newer
+    if test x"$bt_use_am" = xyes; then
+
+	bt_automake=`which automake 2>/dev/null | grep '^/' | head -1`
+	bt_am_version=`$bt_automake --version 2>/dev/null | head -1 \
+		      | sed -e 's/^[^0-9]*//' -e 's/[a-z]* *$//'`
+
+	if test -z "$bt_am_version"; then
+	    bt_error "GNU automake not found in your PATH!"
+	    bt_error "You need automake 1.8 or newer installed."
+
+	    return 1
+	else
+	    save_IFS="$IFS"
+	    IFS=.
+	    set $bt_am_version
+	    IFS="$save_IFS"
+
+	    if test "$1" -lt 1 || test x"$1" = x1 && test "$2" -lt 8; then
+		bt_error "Installed GNU automake $bt_am_version is too old!"
+		bt_error "You need automake 1.8 or newer installed."
+
+		return 1
+	    else
+		bt_info "GNU automake $bt_am_version found."
+		bt_am_path=`dirname $bt_automake`
+	    fi
+	fi
+    fi
+
+
+    # Libtool 1.5 or newer (optional)
+    if test x"$bt_use_lt" = xyes; then
+
+        for f in glibtool libtool; do
+            bt_libtool=`which $f 2>/dev/null | grep '^/' | head -1`
+            test -n "$bt_libtool" && break
+        done
+
+	bt_lt_version=`$bt_libtool --version 2>/dev/null | head -1 \
+		       | sed -e 's/^[^0-9]*//' -e 's/[- ].*//'`
+
+	if test -z "$bt_lt_version"; then
+	    bt_error "GNU libtool not found in your PATH!"
+	    bt_error "You need libtool 1.5 or newer installed."
+
+	    return 1
+	else
+	    save_IFS="$IFS"
+	    IFS=.
+	    set $bt_lt_version
+	    IFS="$save_IFS"
+
+	    if test "$1" -lt 1 || test x"$1" = x1 && test "$2" -lt 5; then
+		bt_error "Installed GNU libtool $bt_lt_version is too old!"
+		bt_error "You need libtool 1.5 or newer installed."
+
+		return 1
+	    else
+		bt_info "GNU libtool $bt_lt_version found."
+		bt_lt_path=`dirname $bt_libtool`
+	    fi
+	fi
+    fi
+
+    PATH="$save_PATH"
+
+    return 0
+
+}
+
+
+bt_setup_libtool ()
+{
+
+    for f in glibtoolize libtoolize; do
+        bt_libtoolize=`which $f 2>/dev/null | grep '^/' | head -1`
+        test -n "$bt_libtoolize" && break
+    done
+
+    if test x"$bt_libtoolize" = x; then
+	bt_error "GNU libtoolize not found in your PATH!"
+    	return 1
+    else
+	bt_lt_path=`dirname $bt_libtoolize`
+    fi
+
+
+    save_cwd=`pwd`
+    cd $bt_auxdir
+
+    bt_lt_files="config.guess config.sub ltmain.sh"
+
+    for f in $bt_lt_files; do
+	if test ! -f $f; then
+	    bt_run_libtoolize=yes
+	    break
+	fi
+    done
+
+    cd $save_cwd
+
+
+    bt_install_ltdl=" "
+
+    if egrep "^AC_LIBLTDL_CONVENIENCE" $bt_configure >/dev/null 2>&1; then
+	bt_install_ltdl="--ltdl"
+    fi
+
+    if test x"$bt_run_libtoolize" = xyes; then
+	bt_info "Copying libtool files."
+	bt_warning "Do not forget to put these files under version control!"
+
+	$bt_libtoolize --automake $bt_copy_files $bt_install_ltdl
+    fi
+
+    # Setup libltdl if the directory exists
+    if test -d libltdl; then
+	bt_info "Setting up libltdl convenience library ..."
+
+	(cd libltdl; $bt_libtoolize --automake $bt_copy_files; \
+         aclocal && autoheader \
+                 && automake --gnits --add-missing $bt_copy_files \
+                 && autoconf) || return 1
+    fi
+
+    return 0
+
+}
+
+
+bt_create_macro_file ()
+{
+
+    # Sanity check
+    if test x"$bt_check_tools" = x1 && test ! -x $bt_am_path/aclocal; then
+        bt_error "Cannot run aclocal. Check your automake installation!"
+        return 1
+    fi
+
+    # Remove aclocal.m4 if it exists
+    test -f ./aclocal.m4 && rm -f ./aclocal.m4
+
+
+    # Recreate aclocal.m4
+    if test -f ./acinclude.m4 || test x"$bt_use_lt" = xyes; then
+	bt_message "Creating aclocal.m4"
+        bt_aclocal_flags=`egrep "^\ *ACLOCAL_AMFLAGS" Makefile.am | \
+                          sed -e 's/^.*=[ ]*//'`
+        if test -n "bt_aclocal_flags"; then
+            aclocal $bt_aclocal_flags || return 1
+        else
+            aclocal || return 1
+        fi
+    fi
+
+    return 0
+
+}
+
+
+bt_create_configure ()
+{
+
+    # Save existing configure
+    test -f ./configure && cp ./configure ./configure.$$.tmp
+
+    # Create the configure script
+    bt_message "Creating configure"
+    autoconf || return 1
+
+    if test -f ./config.cache && test -f ./configure.$$.tmp; then
+	if cmp ./configure ./configure.$$.tmp; then
+	    bt_warning "configure has changed. Removing file config.cache."
+	    rm -f ./config.cache
+	fi
+    fi
+
+    test -f ./configure.$$.tmp && rm -f ./configure.$$.tmp
+
+    return 0
+
+}
+
+
+bt_create_config_header ()
+{
+
+    # Sanity check
+    if test x"$bt_check_tools" = x1 && test ! -x $bt_ac_path/autoheader; then 
+        bt_error "Cannot run autoheader. Check your autoconf installation!"
+        return 1
+    fi
+
+    if egrep "^AM_CONFIG_HEADER" $bt_configure >/dev/null 2>&1; then
+	bt_message "Creating config.h template"
+	autoheader || return 1
+    fi
+
+    return 0
+
+}
+
+
+bt_create_makefile_templates ()
+{
+
+    bt_message "Creating Makefile templates"
+
+    if test x"$1" != xyes; then
+	automake --foreign --add-missing $bt_copy_files
+    else
+	automake --foreign --include-deps
+    fi
+
+    return 0
+
+}
+
+
+bt_bootstrap_packages ()
+{
+
+    if test -r $bt_subdirs_file; then
+	for bt_dir in `cat $bt_subdirs_file`; do
+	    if test ! -d $bt_dir; then
+		bt_warning "Skipping $bt_dir. Directory does not exist!"
+	    else
+		if test ! -x $bt_dir/bootstrap; then
+		    bt_error "Cannot execute $bt_dir/bootstrap."
+		    return 1
+		else
+                    __bt_level=$(($__bt_level + 1))
+		    bt_info "Entering directory \`$PWD/$bt_dir'"
+		    (cd $bt_dir && ./bootstrap -C -L $__bt_level) || return 1
+		    bt_info "Leaving directory \`$PWD/$bt_dir'"
+                    __bt_level=$(($__bt_level - 1))
+		fi
+	    fi
+	done
+    fi
+		    
+    return 0
+
+}
+
+
+bt_main ()
+{
+
+    # Initialize some variables and process the command line
+    bt_init $* || return 1
+
+    # Sanity check
+    bt_check_autoconf_template || return 1
+
+
+    # Check whether optional tools should be used
+    bt_needs_automake
+    bt_needs_libtool
+
+    # Check maintainer tools
+    if test x$bt_check_tools = x1; then
+	bt_check_build_tools || return 1
+    fi
+
+
+    # Setup maintainer tools
+    if test x"$bt_use_lt" = xyes; then
+	bt_setup_libtool || return 1
+    fi
+
+
+    # Bootstrap local build environment
+    if test x"$__bt_level" = x0; then
+        bt_info "Bootstrapping build tree in \`$PWD' ..."
+    fi
+
+    bt_create_macro_file || return 1
+
+    bt_create_configure || return 1
+
+    bt_create_config_header || return 1
+
+    bt_create_makefile_templates $bt_dist_mode || return 1
+
+    
+    # Bootstrap 3rd party packages
+    bt_bootstrap_packages || return 1
+
+    if test $__bt_level -eq 0; then
+        bt_message ""
+        bt_message "Don't forget to run ./configure"
+        bt_message "If you haven't done so in a while, run ./configure --help"
+        bt_message ""
+    fi
+
+    return 0
+
+}
+
+
+# Call main here
+bt_main $* || exit 1
diff --git a/test/iiinstrumentp/configure.ac b/test/iiinstrumentp/configure.ac
new file mode 100644
index 0000000..3bedbde
--- /dev/null
+++ b/test/iiinstrumentp/configure.ac
@@ -0,0 +1,91 @@
+# Process this file with autoconf to produce a configure script.
+
+AC_INIT([IIINSTRUMENT Instrument Pipeline], [0.0.1], [flastname at eso.org],
+[iiinstrument])
+AC_PREREQ([2.59])
+
+AC_CONFIG_MACRO_DIR([m4macros])
+AC_CONFIG_SRCDIR([Makefile.am])
+AC_CONFIG_AUX_DIR([admin])
+
+AC_CANONICAL_HOST
+AC_CANONICAL_TARGET
+
+AM_INIT_AUTOMAKE
+AM_CONFIG_HEADER([config.h])
+
+AM_MAINTAINER_MODE
+
+IIINSTRUMENT_SET_PREFIX(${PIPE_HOME:-/usr/local})
+
+# Immediately before every release do:
+#-------------------------------------
+#   if (the interface is totally unchanged from previous release)
+#       REVISION++;
+#   else {
+#       /* interfaces have been added, removed or changed */
+#       REVISION = 0;
+#       CURRENT++;
+#       if (any interfaces have been _added_ since last release)
+#           AGE++;
+#       if (any interfaces have been _removed_ or incompatibly changed)
+#           AGE = 0;
+#   }
+
+IIINSTRUMENT_SET_VERSION_INFO([$VERSION])
+
+# Checks for programs.
+AC_PROG_CC
+AC_PROG_CPP
+AM_PROG_CC_C_O
+AC_PROG_LN_S
+
+#
+# Check for purify
+#
+CHECK_PURIFY
+
+
+ESO_ENABLE_DEBUG(no)
+ESO_ENABLE_STRICT(no)
+ESO_PROG_CC_FLAG([fno-builtin], [CFLAGS="$CFLAGS -fno-builtin"])
+ESO_PROG_CC_FLAG([-std=c99], [CFLAGS="$CFLAGS --std=c99"])
+
+ESO_CHECK_DOCTOOLS
+
+AC_ENABLE_STATIC(no)
+AC_ENABLE_SHARED(yes)
+
+AC_PROG_LIBTOOL
+AC_SUBST(LIBTOOL_DEPS)
+
+# Checks for libraries.
+AC_CHECK_LIB(m, pow, [LIBS="$LIBS -lm"])
+AC_CHECK_LIB(socket, socket, [LIBS="$LIBS -lsocket"])
+AC_CHECK_LIB(nsl, inet_ntoa, [LIBS="$LIBS -lnsl"])
+
+# Checks for header files.
+AC_HEADER_STDC
+AC_CHECK_HEADERS([string.h])
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_C_CONST
+AC_C_INLINE
+
+# Checks for library functions.
+AC_CHECK_FUNCS([floor pow sqrt isinf isnan])
+ESO_FUNC_STRDUP
+
+# Check for CPL presence and usability
+CPL_CHECK_LIBS
+
+IIINSTRUMENT_SET_PATHS
+IIINSTRUMENT_CREATE_SYMBOLS
+
+AM_WITH_DMALLOC
+
+
+AC_CONFIG_FILES(Makefile
+                iiinstrument/Makefile
+                recipes/Makefile)
+AC_OUTPUT
diff --git a/test/iiinstrumentp/iiinstrument/Makefile.am b/test/iiinstrumentp/iiinstrument/Makefile.am
new file mode 100644
index 0000000..0a87ce7
--- /dev/null
+++ b/test/iiinstrumentp/iiinstrument/Makefile.am
@@ -0,0 +1,42 @@
+## Process this file with automake to produce Makefile.in
+
+##   This file is part of the IIINSTRUMENT Pipeline
+##   Copyright (C) 2002,2003 European Southern Observatory
+
+##   This library 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+AUTOMAKE_OPTIONS = 1.8 foreign
+
+DISTCLEANFILES = *~
+
+CPPFLAGS = -DCX_LOG_DOMAIN=\"IiinstrumentLib\"
+
+INCLUDES = $(all_includes)
+
+noinst_HEADERS =    iiinstrument_utils.h \
+                    iiinstrument_pfits.h \
+                    iiinstrument_dfs.h
+
+pkginclude_HEADERS =
+
+lib_LTLIBRARIES = libiiinstrument.la
+
+libiiinstrument_la_SOURCES =    iiinstrument_utils.c \
+                                iiinstrument_pfits.c \
+                                iiinstrument_dfs.c
+
+libiiinstrument_la_LDFLAGS = $(CPL_LDFLAGS) -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE)
+libiiinstrument_la_LIBADD = $(LIBCPLDFS) $(LIBCPLUI) $(LIBCPLDRS) $(LIBCPLCORE) 
+libiiinstrument_la_DEPENDENCIES =
diff --git a/test/iiinstrumentp/iiinstrument/iiinstrument_dfs.c b/test/iiinstrumentp/iiinstrument/iiinstrument_dfs.c
new file mode 100644
index 0000000..f1ba8b2
--- /dev/null
+++ b/test/iiinstrumentp/iiinstrument/iiinstrument_dfs.c
@@ -0,0 +1,93 @@
+/* $Id: iiinstrument_dfs.c,v 1.6 2007/07/31 06:10:40 llundin Exp $
+ *
+ * This file is part of the IIINSTRUMENT Pipeline
+ * Copyright (C) 2002,2003 European Southern Observatory
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ * $Author: llundin $
+ * $Date: 2007/07/31 06:10:40 $
+ * $Revision: 1.6 $
+ * $Name:  $
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/*-----------------------------------------------------------------------------
+                                   Includes
+ -----------------------------------------------------------------------------*/
+
+#include <string.h>
+#include <math.h>
+
+#include <cpl.h>
+
+#include "iiinstrument_dfs.h"
+
+/*----------------------------------------------------------------------------*/
+/**
+ * @defgroup iiinstrument_dfs  DFS related functions
+ *
+ * TBD
+ */
+/*----------------------------------------------------------------------------*/
+
+/**@{*/
+
+/*----------------------------------------------------------------------------*/
+/**
+  @brief    Set the group as RAW or CALIB in a frameset
+  @param    set     the input frameset
+  @return   CPL_ERROR_NONE iff OK
+ */
+/*----------------------------------------------------------------------------*/
+cpl_error_code iiinstrument_dfs_set_groups(cpl_frameset * set)
+{
+    cpl_errorstate prestate = cpl_errorstate_get();
+    cpl_frame * frame = NULL;
+    int         i = 0;
+
+
+    /* Loop on frames */
+    for (frame = cpl_frameset_get_first(set); frame != NULL;
+         frame = cpl_frameset_get_next(set), i++) {
+
+        const char * tag = cpl_frame_get_tag(frame);
+
+        if (tag == NULL) {
+            cpl_msg_warning(cpl_func, "Frame %d has no tag", i);
+        } else if (!strcmp(tag, RRRECIPE_RAW)) {
+            /* RAW frames */
+            cpl_frame_set_group(frame, CPL_FRAME_GROUP_RAW);
+        } else if (!strcmp(tag, IIINSTRUMENT_CALIB_FLAT)) {
+            /* CALIB frames */
+            cpl_frame_set_group(frame, CPL_FRAME_GROUP_CALIB);
+        }
+    }
+
+    if (!cpl_errorstate_is_equal(prestate)) {
+        return cpl_error_set_message(cpl_func, cpl_error_get_code(),
+                                     "Could not identify RAW and CALIB "
+                                     "frames");
+    }
+
+    return CPL_ERROR_NONE;
+}
+
+/**@}*/
diff --git a/test/iiinstrumentp/iiinstrument/iiinstrument_dfs.h b/test/iiinstrumentp/iiinstrument/iiinstrument_dfs.h
new file mode 100644
index 0000000..fd0dd8e
--- /dev/null
+++ b/test/iiinstrumentp/iiinstrument/iiinstrument_dfs.h
@@ -0,0 +1,48 @@
+/* $Id: iiinstrument_dfs.h,v 1.9 2007/07/31 06:10:40 llundin Exp $
+ *
+ * This file is part of the IIINSTRUMENT Pipeline
+ * Copyright (C) 2002,2003 European Southern Observatory
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ * $Author: llundin $
+ * $Date: 2007/07/31 06:10:40 $
+ * $Revision: 1.9 $
+ * $Name:  $
+ */
+
+#ifndef IIINSTRUMENT_DFS_H
+#define IIINSTRUMENT_DFS_H
+
+/*-----------------------------------------------------------------------------
+                                   Define
+ -----------------------------------------------------------------------------*/
+
+/* Define here the PRO.CATG keywords */
+#define RRRECIPE_XXX_PROCATG            "THE_PRO_CATG_VALUE"
+
+/* Define here the DO.CATG keywords */
+#define RRRECIPE_RAW                    "RRRECIPE_DOCATG_RAW"
+#define IIINSTRUMENT_CALIB_FLAT         "FLAT"
+
+/*-----------------------------------------------------------------------------
+                                Functions prototypes
+ -----------------------------------------------------------------------------*/
+
+cpl_error_code iiinstrument_dfs_set_groups(cpl_frameset *);
+
+#endif
diff --git a/test/iiinstrumentp/iiinstrument/iiinstrument_pfits.c b/test/iiinstrumentp/iiinstrument/iiinstrument_pfits.c
new file mode 100644
index 0000000..cd23ab9
--- /dev/null
+++ b/test/iiinstrumentp/iiinstrument/iiinstrument_pfits.c
@@ -0,0 +1,88 @@
+/* $Id: iiinstrument_pfits.c,v 1.12 2007/07/31 06:10:40 llundin Exp $
+ *
+ * This file is part of the IIINSTRUMENT Pipeline
+ * Copyright (C) 2002,2003 European Southern Observatory
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ * $Author: llundin $
+ * $Date: 2007/07/31 06:10:40 $
+ * $Revision: 1.12 $
+ * $Name:  $
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/*-----------------------------------------------------------------------------
+                                   Includes
+ -----------------------------------------------------------------------------*/
+
+#include <cpl.h>
+
+#include "iiinstrument_pfits.h"
+
+/*----------------------------------------------------------------------------*/
+/**
+ * @defgroup iiinstrument_pfits     FITS header protected access
+ *
+ */
+/*----------------------------------------------------------------------------*/
+
+/**@{*/
+
+/*-----------------------------------------------------------------------------
+                              Function codes
+ -----------------------------------------------------------------------------*/
+
+/*----------------------------------------------------------------------------*/
+/**
+  @brief    find out the arcfile   
+  @param    plist       property list to read from
+  @return   pointer to statically allocated character string
+ */
+/*----------------------------------------------------------------------------*/
+const char * iiinstrument_pfits_get_arcfile(const cpl_propertylist * plist)
+{
+    const char * value = cpl_propertylist_get_string(plist, "ARCFILE");
+
+    cpl_ensure(value != NULL, cpl_error_get_code(), NULL);
+
+    return value;
+}
+
+/*----------------------------------------------------------------------------*/
+/**
+  @brief    find out the DIT value 
+  @param    plist       property list to read from
+  @return   the requested value
+ */
+/*----------------------------------------------------------------------------*/
+double iiinstrument_pfits_get_dit(const cpl_propertylist * plist)
+{
+    cpl_errorstate prestate = cpl_errorstate_get();
+    const double value = cpl_propertylist_get_double(plist, "ESO DET DIT");
+
+    /* Check for a change in the CPL error state */
+    /* - if it did change then propagate the error and return */
+    cpl_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(), 0.0);
+
+    return value;
+}
+
+/**@}*/
diff --git a/test/iiinstrumentp/iiinstrument/iiinstrument_pfits.h b/test/iiinstrumentp/iiinstrument/iiinstrument_pfits.h
new file mode 100644
index 0000000..0b02ab3
--- /dev/null
+++ b/test/iiinstrumentp/iiinstrument/iiinstrument_pfits.h
@@ -0,0 +1,44 @@
+/* $Id: iiinstrument_pfits.h,v 1.8 2007/07/31 06:10:40 llundin Exp $
+ *
+ * This file is part of the IIINSTRUMENT Pipeline
+ * Copyright (C) 2002,2003 European Southern Observatory
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ * $Author: llundin $
+ * $Date: 2007/07/31 06:10:40 $
+ * $Revision: 1.8 $
+ * $Name:  $
+ */
+
+#ifndef IIINSTRUMENT_PFITS_H
+#define IIINSTRUMENT_PFITS_H
+
+/*-----------------------------------------------------------------------------
+                                   Includes
+ -----------------------------------------------------------------------------*/
+
+#include <cpl.h>
+
+/*-----------------------------------------------------------------------------
+                                   Functions prototypes
+ -----------------------------------------------------------------------------*/
+
+const char * iiinstrument_pfits_get_arcfile(const cpl_propertylist *) ;
+double iiinstrument_pfits_get_dit(const cpl_propertylist *) ;
+
+#endif
diff --git a/test/iiinstrumentp/iiinstrument/iiinstrument_utils.c b/test/iiinstrumentp/iiinstrument/iiinstrument_utils.c
new file mode 100644
index 0000000..837db31
--- /dev/null
+++ b/test/iiinstrumentp/iiinstrument/iiinstrument_utils.c
@@ -0,0 +1,80 @@
+/* $Id: iiinstrument_utils.c,v 1.10 2007/07/31 06:10:40 llundin Exp $
+ *
+ * This file is part of the IIINSTRUMENT Pipeline
+ * Copyright (C) 2002,2003 European Southern Observatory
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ * $Author: llundin $
+ * $Date: 2007/07/31 06:10:40 $
+ * $Revision: 1.10 $
+ * $Name:  $
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/*-----------------------------------------------------------------------------
+                                   Includes
+ -----------------------------------------------------------------------------*/
+
+#include <cpl.h>
+
+#include "iiinstrument_utils.h"
+
+/*----------------------------------------------------------------------------*/
+/**
+ * @defgroup iiinstrument_utils     Miscellaneous Utilities
+ */
+/*----------------------------------------------------------------------------*/
+
+/**@{*/
+
+/*----------------------------------------------------------------------------*/
+/**
+  @brief    Get the pipeline copyright and license
+  @return   The copyright and license string
+
+  The function returns a pointer to the statically allocated license string.
+  This string should not be modified using the returned pointer.
+ */
+/*----------------------------------------------------------------------------*/
+const char * iiinstrument_get_license(void)
+{
+    const char  *   iiinstrument_license = 
+        "This file is part of the IIINSTRUMENT Instrument Pipeline\n"
+        "Copyright (C) 2002,2003 European Southern Observatory\n"
+        "\n"
+        "This program is free software; you can redistribute it and/or modify\n"
+        "it under the terms of the GNU General Public License as published by\n"
+        "the Free Software Foundation; either version 2 of the License, or\n"
+        "(at your option) any later version.\n"
+        "\n"
+        "This program is distributed in the hope that it will be useful,\n"
+        "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+        "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
+        "GNU General Public License for more details.\n"
+        "\n"
+        "You should have received a copy of the GNU General Public License\n"
+        "along with this program; if not, write to the Free Software\n"
+        "Foundation, Inc., 59 Temple Place, Suite 330, Boston, \n"
+        "MA  02111-1307  USA" ;
+    return iiinstrument_license ;
+}
+
+/**@}*/
diff --git a/test/iiinstrumentp/iiinstrument/iiinstrument_utils.h b/test/iiinstrumentp/iiinstrument/iiinstrument_utils.h
new file mode 100644
index 0000000..9464dd4
--- /dev/null
+++ b/test/iiinstrumentp/iiinstrument/iiinstrument_utils.h
@@ -0,0 +1,43 @@
+/* $Id: iiinstrument_utils.h,v 1.12 2007/07/31 06:10:40 llundin Exp $
+ *
+ * This file is part of the IIINSTRUMENT Pipeline
+ * Copyright (C) 2002,2003 European Southern Observatory
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ * $Author: llundin $
+ * $Date: 2007/07/31 06:10:40 $
+ * $Revision: 1.12 $
+ * $Name:  $
+ */
+
+#ifndef IIINSTRUMENT_UTILS_H
+#define IIINSTRUMENT_UTILS_H
+
+/*-----------------------------------------------------------------------------
+                                   Includes
+ -----------------------------------------------------------------------------*/
+
+#include <cpl.h>
+
+/*-----------------------------------------------------------------------------
+                                       Prototypes
+ -----------------------------------------------------------------------------*/
+
+const char * iiinstrument_get_license(void) ;
+
+#endif
diff --git a/test/iiinstrumentp/m4macros/cpl.m4 b/test/iiinstrumentp/m4macros/cpl.m4
new file mode 100644
index 0000000..00442b2
--- /dev/null
+++ b/test/iiinstrumentp/m4macros/cpl.m4
@@ -0,0 +1,363 @@
+# CPL_CHECK_CFITSIO
+#------------------
+# Checks for the cfitsio library and header files.
+AC_DEFUN([CPL_CHECK_CFITSIO],
+[
+    cpl_cfitsio_check_header="fitsio.h"
+    # FIXME: Check first for the dynamic library...
+    cpl_cfitsio_check_lib="libcfitsio.so"
+
+    cpl_cfitsio_dir=""
+    cpl_cfitsio_incdirs=""
+    cpl_cfitsio_libdirs=""
+    cpl_cfitsio_includes=""
+    cpl_cfitsio_libraries=""
+
+    # Get the CFITSIO directory from the --with-cfitsio CLI option, or
+    # else fall back on the environment variable $CFITSIODIR
+    AC_ARG_WITH(cfitsio,
+                AC_HELP_STRING([--with-cfitsio],
+                               [location where cfitsio is installed]),
+                [
+                    cpl_cfitsio_dir=$withval
+                ],
+                [
+                    cpl_cfitsio_dir=$CFITSIODIR
+                ])
+
+    # Check for the cfitsio includes, either in include/, include/cfitsio/
+    # or include/libcfitsio0/
+    if test -n "$cpl_cfitsio_dir" ; then
+      AC_MSG_CHECKING([for cfitsio in $cpl_cfitsio_dir])
+      cpl_cfitsio_incdirs="$cpl_cfitsio_dir/include"
+      ESO_FIND_FILE($cpl_cfitsio_check_header, $cpl_cfitsio_incdirs, cpl_cfitsio_includes)
+      if test x"$cpl_cfitsio_includes" = xno; then
+        # include/cfitsio/ is seen on a 64-bit Fedora 10
+        cpl_cfitsio_incdirs="$cpl_cfitsio_dir/include/cfitsio"
+        ESO_FIND_FILE($cpl_cfitsio_check_header, $cpl_cfitsio_incdirs, cpl_cfitsio_includes)
+        if test x"$cpl_cfitsio_includes" = xno; then
+          # include/libcfitsio0/ is seen on a 64-bit Suse
+          cpl_cfitsio_incdirs="$cpl_cfitsio_dir/include/libcfitsio0"
+          ESO_FIND_FILE($cpl_cfitsio_check_header, $cpl_cfitsio_incdirs, cpl_cfitsio_includes)
+        fi
+      fi
+
+      # Check for the cfitsio library, either in lib64/, lib32/ or lib/
+      cpl_cfitsio_libdirs="$cpl_cfitsio_dir/lib64"
+      ESO_FIND_FILE($cpl_cfitsio_check_lib, $cpl_cfitsio_libdirs, cpl_cfitsio_libraries)
+
+      if test x"$cpl_cfitsio_libraries" = xno; then
+        cpl_cfitsio_libdirs="$cpl_cfitsio_dir/lib32"
+        ESO_FIND_FILE($cpl_cfitsio_check_lib, $cpl_cfitsio_libdirs, cpl_cfitsio_libraries)
+        if test x"$cpl_cfitsio_libraries" = xno; then
+          cpl_cfitsio_libdirs="$cpl_cfitsio_dir/lib"
+          ESO_FIND_FILE($cpl_cfitsio_check_lib, $cpl_cfitsio_libdirs, cpl_cfitsio_libraries)
+        fi
+      fi
+
+      if test x"$cpl_cfitsio_includes" = xno || test x"$cpl_cfitsio_libraries" = xno; then
+          AC_MSG_ERROR([cfitsio was not found on your system. Please check!])
+      else
+          AC_MSG_RESULT([libraries $cpl_cfitsio_libraries, headers $cpl_cfitsio_includes])
+          # Attempt to check the version via CFITSIO_VERSION in the include file
+          cpl_cfitsio_check_vers=`perl -nle 's/^#\s*define\s+CFITSIO_VERSION\s+\b// and s/\s.*//, print' $cpl_cfitsio_inclu
+des/fitsio.h`
+         if test -z "$cpl_cfitsio_check_vers" ; then
+           # Attempt to check the version by checking the include files
+           cpl_cfitsio_check_vers=`grep "Version Info: This file is distributed with version 2.510 of CFITSIO" $cpl_cfitsio
+_includes/fitsio.h`
+           if test -z "$cpl_cfitsio_check_vers" ; then
+               AC_MSG_WARN([cfitsio version seems to be different from 2.510 and less than 3.X.])
+           else
+               AC_MSG_WARN([cfitsio version seems to be 2.510])
+           fi
+         else
+            CFLAGS="-D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 $CFLAGS"
+         fi
+      fi
+
+      # Set up the symbols
+      CFITSIO_INCLUDES="-I$cpl_cfitsio_includes"
+      CFITSIO_LDFLAGS="-L$cpl_cfitsio_libraries"
+    else
+      AC_CHECK_HEADERS($cpl_cfitsio_check_header,,AC_MSG_ERROR([fitsio.h was not found on your system. Please check!]))
+      AC_SEARCH_LIBS([fits_get_cwd], [cfitsio],,AC_MSG_ERROR([libcfitsio was not found on your system. Please check!]),[-lpthread])
+      CFLAGS="-D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 $CFLAGS"
+    fi
+
+    LIBCFITSIO="-lcfitsio"
+    AC_SUBST(CFITSIO_INCLUDES)
+    AC_SUBST(CFITSIO_LDFLAGS)
+    AC_SUBST(LIBCFITSIO)
+])
+
+# CPL_CHECK_WCS
+#--------------
+# Checks for the wcs library and header files.
+AC_DEFUN([CPL_CHECK_WCS],
+[
+    AC_MSG_CHECKING([for wcs])
+
+    cpl_wcs_check_header="wcslib/wcslib.h"
+    cpl_wcs_check_lib="libwcs.a"
+
+    cpl_wcs_includes=""
+    cpl_wcs_libraries=""
+
+    AC_ARG_WITH(wcs,
+                AC_HELP_STRING([--with-wcs],
+                               [location where wcs is installed]),
+                [
+                    cpl_with_wcs_includes=$withval/include
+                    cpl_with_wcs_libs=$withval/lib
+                ])
+
+    if test -z "$cpl_with_wcs_includes"; then
+        test -n "$WCSDIR" && cpl_wcs_incdirs="$WCSDIR/include"
+    else
+        cpl_wcs_incdirs="$cpl_with_wcs_includes"
+    fi
+
+    if test -z "$cpl_with_wcs_libs"; then
+        test -n "$WCSDIR" && cpl_wcs_libdirs="$WCSDIR/lib"
+    else
+        cpl_wcs_libdirs="$cpl_with_wcs_libs"
+    fi
+
+    if test -n "$cpl_with_wcs_includes"; then
+      # Check for the wcs includes
+      ESO_FIND_FILE($cpl_wcs_check_header, $cpl_wcs_incdirs, cpl_wcs_includes)
+    else
+      AC_CHECK_HEADERS($cpl_wcs_check_header,,cpl_wcs_includes="no")
+    fi
+
+    # Check for the wcs library
+    if test -n "$cpl_with_wcs_libs"; then
+      ESO_FIND_FILE($cpl_wcs_check_lib, $cpl_wcs_libdirs, cpl_wcs_libraries)
+    else
+      AC_SEARCH_LIBS([wcsini], [wcs],,cpl_wcs_libraries="no",)
+    fi
+
+    if test x"$cpl_wcs_includes" = xno || test x"$cpl_wcs_libraries" = xno; then
+        AC_MSG_WARN([wcs was not found on your system.])
+    else
+        AC_MSG_RESULT([libraries $cpl_wcs_libraries, headers $cpl_wcs_includes])
+        if test -n "$cpl_wcs_includes" ; then
+          # Attempt to check the version by checking the include files
+          cpl_wcs_check_vers43=`grep "WCSLIB 4.3 - an implementation of the FITS WCS standard" $cpl_wcs_includes/wcslib/wcslib.h`
+          if test -z "$cpl_wcs_check_vers43" ; then
+              cpl_wcs_check_vers44=`grep "WCSLIB 4.4 - an implementation of the FITS WCS standard" $cpl_wcs_includes/wcslib/wcslib.h`
+              if test -z "$cpl_wcs_check_vers44" ; then
+                  AC_MSG_WARN([wcs version seems to be older than 4.3])
+              fi
+          fi
+        fi
+        AC_DEFINE_UNQUOTED(CPL_WCS_INSTALLED, 1, [Defined if WCS is available])
+        # Set up the symbols
+        if test -n "$cpl_wcs_includes" ; then
+          WCS_INCLUDES="-I$cpl_wcs_includes"
+        fi
+        if test -n "$cpl_wcs_libraries" ; then
+          WCS_LDFLAGS="-L$cpl_wcs_libraries"
+        fi
+        LIBWCS="-lwcs"
+
+        AC_SUBST(WCS_INCLUDES)
+        AC_SUBST(WCS_LDFLAGS)
+        AC_SUBST(LIBWCS)
+    fi
+])
+
+# CPL_CHECK_FFTW
+#--------------
+# Checks for the wcs library and header files.
+AC_DEFUN([CPL_CHECK_FFTW],
+[
+    AC_MSG_CHECKING([for fftw])
+
+    cpl_fftw_check_header="fftw3.h"
+    cpl_fftwf_check_header="fftw3.h"
+    cpl_fftw_check_lib="libfftw3.a"
+    cpl_fftwf_check_lib="libfftw3f.a"
+
+    cpl_fftw_includes=""
+    cpl_fftwf_includes=""
+    cpl_fftw_libraries=""
+    cpl_fftwf_libraries=""
+
+    AC_ARG_WITH(fftw,
+                AC_HELP_STRING([--with-fftw],
+                               [location where fftw is installed]),
+                [
+                    cpl_with_fftw_includes=$withval/include
+                    cpl_with_fftw_libs=$withval/lib
+                ])
+
+    if test -z "$cpl_with_fftw_includes"; then
+        test -n "$FFTWDIR" && cpl_fftw_incdirs="$FFTWDIR/include"
+    else
+        cpl_fftw_incdirs="$cpl_with_fftw_includes"
+    fi
+    if test -z "$cpl_with_fftw_libs"; then
+        test -n "$FFTWDIR" && cpl_fftw_libdirs="$FFTWDIR/lib"
+    else
+        cpl_fftw_libdirs="$cpl_with_fftw_libs"
+    fi
+
+    # Check for the fftw includes
+    if test -n "$cpl_fftw_incdirs"; then
+      ESO_FIND_FILE($cpl_fftw_check_header, $cpl_fftw_incdirs, cpl_fftw_includes)
+      ESO_FIND_FILE($cpl_fftwf_check_header, $cpl_fftw_incdirs, cpl_fftwf_includes)
+    else
+      AC_CHECK_HEADERS($cpl_fftw_check_header,,cpl_fftw_includes="no")
+      AC_CHECK_HEADERS($cpl_fftwf_check_header,,cpl_fftwf_includes="no")
+    fi
+
+    # Check for the fftw library
+    if test -n "$cpl_fftw_libdirs"; then
+      ESO_FIND_FILE($cpl_fftw_check_lib, $cpl_fftw_libdirs, cpl_fftw_libraries)
+      ESO_FIND_FILE($cpl_fftwf_check_lib, $cpl_fftw_libdirs, cpl_fftwf_libraries)
+    else
+      AC_SEARCH_LIBS([fftw_version], [fftw3],,cpl_fftw_libraries="no",)
+      AC_SEARCH_LIBS([fftwf_version], [fftw3f],,cpl_fftwf_libraries="no",)
+    fi
+
+    if test x"$cpl_fftw_includes" = xno || test x"$cpl_fftw_libraries" = xno; then
+        AC_MSG_WARN([fftw (normal-precision) was not found on your system.])
+    else
+        AC_MSG_RESULT([libraries $cpl_fftw_libraries, headers $cpl_fftw_includes])
+        # FIXME: Attempt to check the version
+
+        AC_DEFINE_UNQUOTED(CPL_FFTW_INSTALLED, 1, [Defined if FFTW (normal-precision) is available])
+        # Set up the symbols
+        if test -n "$cpl_fftw_includes"; then
+          FFTW_INCLUDES="-I$cpl_fftw_includes"
+        fi
+        if test -n "$cpl_fftw_libraries"; then
+          FFTW_LDFLAGS="-L$cpl_fftw_libraries"
+        fi
+        LIBFFTW="-lfftw3"
+
+        AC_SUBST(FFTW_INCLUDES)
+        AC_SUBST(FFTW_LDFLAGS)
+        AC_SUBST(LIBFFTW)
+    fi
+
+    if test x"$cpl_fftwf_includes" = xno || test x"$cpl_fftwf_libraries" = xno; then
+        AC_MSG_WARN([fftw (single-precision) was not found on your system.])
+    else
+        AC_MSG_RESULT([libraries $cpl_fftwf_libraries, headers $cpl_fftwf_includes])
+        # FIXME: Attempt to check the version
+
+        AC_DEFINE_UNQUOTED(CPL_FFTWF_INSTALLED, 1, [Defined if FFTW (single-precision) is available])
+        # Set up the symbols
+        if test -n "$cpl_fftwf_includes"; then
+          FFTWF_INCLUDES="-I$cpl_fftwf_includes"
+        fi
+        if test -n "$cpl_fftwf_libraries"; then
+          FFTWF_LDFLAGS="-L$cpl_fftwf_libraries"
+        fi
+        LIBFFTWF="-lfftw3f"
+
+        AC_SUBST(FFTWF_INCLUDES)
+        AC_SUBST(FFTWF_LDFLAGS)
+        AC_SUBST(LIBFFTWF)
+    fi
+
+])
+
+#
+# CPL_CREATE_SYMBOLS(build=[])
+#-----------------------------
+# Sets the Makefile symbols for the CPL libraries. If an argument is
+# provided the symbols are setup for building CPL, if no argument is
+# given (default) the symbols are set for using the libraries
+# for external package development.
+AC_DEFUN([CPL_CREATE_SYMBOLS],
+[
+
+    if test -z "$1"; then
+        LIBCPLCORE='-lcplcore'
+        LIBCPLDRS='-lcpldrs'
+        LIBCPLUI='-lcplui'
+        LIBCPLDFS='-lcpldfs'
+    else
+        LIBCPLCORE='$(top_builddir)/cplcore/libcplcore.la'
+        LIBCPLDRS='$(top_builddir)/cpldrs/libcpldrs.la'
+        LIBCPLUI='$(top_builddir)/cplui/libcplui.la'
+        LIBCPLDFS='$(top_builddir)/cpldfs/libcpldfs.la'
+    fi
+
+   AC_SUBST(LIBCPLCORE)
+   AC_SUBST(LIBCPLDRS)
+   AC_SUBST(LIBCPLUI)
+   AC_SUBST(LIBCPLDFS)
+
+])
+
+
+# CPL_CHECK_LIBS
+#---------------
+# Checks for the CPL libraries and header files.
+AC_DEFUN([CPL_CHECK_LIBS],
+[
+
+    AC_REQUIRE([CPL_CHECK_CFITSIO])
+    
+    cpl_check_cpl_header="cpl.h"
+    cpl_check_cpl_lib="libcplcore.so"
+
+    cpl_incdirs=""
+    cpl_libdirs=""
+    cpl_includes=""
+    cpl_libraries=""
+
+    AC_ARG_WITH(cpl,
+                AC_HELP_STRING([--with-cpl],
+                               [location where CPL is installed]),
+                [
+                    cpl_with_cpl=$withval
+                ])
+
+    if test -n "$cpl_with_cpl" ; then
+      AC_MSG_CHECKING([for CPL])
+      cpl_incdirs="$cpl_with_cpl/include"
+      ESO_FIND_FILE($cpl_check_cpl_header, $cpl_incdirs, cpl_includes)
+        
+      cpl_libdirs="$cpl_with_cpl/lib"
+      ESO_FIND_FILE($cpl_check_cpl_lib, $cpl_libdirs, cpl_libraries)
+
+      if test x"$cpl_includes" = xno || test x"$cpl_libraries" = xno; then
+          cpl_notfound=""
+          if test x"$cpl_includes" = xno; then
+              if test x"$cpl_libraries" = xno; then
+                  cpl_notfound="(headers and libraries)"
+              else
+                  cpl_notfound="(headers)"
+              fi
+          else
+              cpl_notfound="(libraries)"
+          fi
+
+          AC_MSG_ERROR([CPL $cpl_notfound was not found on your system. Please check!])
+      else
+          AC_MSG_RESULT([libraries $cpl_libraries, headers $cpl_includes])
+          # Set up the symbols
+
+          CPL_INCLUDES="-I$cpl_includes"
+          CPL_LDFLAGS="-L$cpl_libraries"
+      fi
+    else
+       AC_CHECK_HEADERS($cpl_check_cpl_header,,AC_MSG_ERROR([cpl.h was not found on your system. Please check!]))
+       AC_SEARCH_LIBS([cpl_init], [cplcore],,AC_MSG_ERROR([libcplcore was not found on your system. Please check!]),)
+    fi
+
+    CPL_CREATE_SYMBOLS
+     AC_SUBST(CPL_INCLUDES)
+    AC_SUBST(CPL_LDFLAGS)
+    AC_SUBST(LIBCPLCORE)
+    AC_SUBST(LIBCPLDRS)
+    AC_SUBST(LIBCPLUI)
+    AC_SUBST(LIBCPLDFS)
+
+])
diff --git a/test/iiinstrumentp/m4macros/eso.m4 b/test/iiinstrumentp/m4macros/eso.m4
new file mode 100644
index 0000000..53cf8d7
--- /dev/null
+++ b/test/iiinstrumentp/m4macros/eso.m4
@@ -0,0 +1,1019 @@
+# ESO_PROG_CC_FLAG(FLAG, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+#-----------------------------------------------------------------
+AC_DEFUN([ESO_PROG_CC_FLAG],
+[
+    AC_REQUIRE([AC_PROG_CC])
+
+    flag=`echo $1 | sed 'y%.=/+-%___p_%'`
+    AC_CACHE_CHECK([whether $CC supports -$1],
+                   [eso_cv_prog_cc_$flag],
+                   [
+                       eval "eso_cv_prog_cc_$flag=no"
+                       AC_LANG_PUSH(C)
+
+                       echo 'int main() { return 0; }' >conftest.$ac_ext
+
+                       try_compile="`$CC -$1 -c conftest.$ac_ext 2>&1`"
+                       if test -z "$try_compile"; then
+                           try_link="`$CC -$1 -o conftest$ac_exeext \
+                                    conftest.$ac_ext 2>&1`"
+                           if test -z "$try_link"; then
+                               eval "eso_cv_prog_cc_$flag=yes"
+                           fi
+                       fi
+                       rm -f conftest*
+
+                       AC_LANG_POP(C)
+                   ])
+
+    if eval "test \"`echo '$eso_cv_prog_cc_'$flag`\" = yes"; then
+        :
+        $2
+    else
+        :
+        $3
+    fi
+])
+
+
+# ESO_ENABLE_DEBUG(debug=no)
+#---------------------------
+AC_DEFUN([ESO_ENABLE_DEBUG],
+[
+    AC_REQUIRE([AC_PROG_CC])
+
+    AC_ARG_ENABLE(debug,
+                  AC_HELP_STRING([--enable-debug],
+                                 [creates debugging code [[default=$1]]]),
+                  eso_enable_debug=$enableval, eso_enable_debug=$1)
+
+    AC_CACHE_CHECK([whether debugging code should be created],
+                   eso_cv_enable_debug,
+                   eso_cv_enable_debug=$eso_enable_debug)
+
+    if test x"$eso_cv_enable_debug" = xyes; then
+
+        eso_clean_CFLAGS="`echo $CFLAGS | sed -e 's/-O[[0-9]]//g' \
+                                              -e 's/-g[[0-9]]//g' \
+                                              -e 's/-g[[a-z,A-Z]]* / /g' \
+                                              -e 's/-[[Og]]//g'`"
+
+        ESO_PROG_CC_FLAG([g3], [CFLAGS="$CFLAGS -g3"])
+
+        if test x"$eso_cv_prog_cc_g3" = xyes; then
+            CFLAGS="-g3"
+        else
+            if test x"$ac_cv_prog_cc_g" = xyes; then
+                CFLAGS="-g"
+            else
+                CFLAGS=""
+            fi
+        fi
+
+        ESO_PROG_CC_FLAG([ggdb], [CFLAGS="$CFLAGS -ggdb"])
+        ESO_PROG_CC_FLAG([O0], [CFLAGS="$CFLAGS -O0"])
+        ESO_PROG_CC_FLAG([rdynamic], [CFLAGS="$CFLAGS -rdynamic"])
+        ESO_PROG_CC_FLAG([Wall], [CFLAGS="$CFLAGS -Wall"])
+        ESO_PROG_CC_FLAG([W], [CFLAGS="$CFLAGS -W"])
+
+        CFLAGS="$CFLAGS $eso_clean_CFLAGS"
+        ESO_DEBUG_FLAGS="-DESO_ENABLE_DEBUG"
+    else
+        ESO_DEBUG_FLAGS="-DNDEBUG"
+    fi
+
+    AC_SUBST(ESO_DEBUG_FLAGS)
+])
+
+
+# ESO_ENABLE_STRICT(strict=no)
+#-----------------------------
+AC_DEFUN([ESO_ENABLE_STRICT],
+[
+    AC_REQUIRE([AC_PROG_CC])
+
+    AC_ARG_ENABLE(strict,
+                  AC_HELP_STRING([--enable-strict],
+                                 [compiles with strict compiler options (may not work!) [[default=$1]]]),
+                  eso_enable_strict=$enableval, eso_enable_strict=$1)
+
+    AC_CACHE_CHECK([whether strict compiler options should be used],
+                   eso_cv_enable_strict,
+                   eso_cv_enable_strict=$eso_enable_strict)
+
+
+    if test x"$eso_cv_enable_strict" = xyes; then
+        ESO_PROG_CC_FLAG([std=c99], [CFLAGS="$CFLAGS -std=c99"])
+        ESO_PROG_CC_FLAG([pedantic], [CFLAGS="$CFLAGS -pedantic"])
+    fi
+])
+
+
+# ESO_ENABLE_PROFILE(profile=no)
+#-----------------------------
+AC_DEFUN([ESO_ENABLE_PROFILE],
+[
+    AC_REQUIRE([AC_PROG_CC])
+
+    AC_ARG_ENABLE(profile,
+                  AC_HELP_STRING([--enable-profile],
+                                 [compiles with compiler options necessary for profiling (may not work!) [[default=$1]]]),
+                  eso_enable_profile=$enableval, eso_enable_profile=$1)
+
+    AC_CACHE_CHECK([whether profiling compiler options should be used],
+                   eso_cv_enable_profile,
+                   eso_cv_enable_profile=$eso_enable_profile)
+
+
+    if test x"$eso_cv_enable_profile" = xyes; then
+        ESO_PROG_CC_FLAG([pg], [CFLAGS="$CFLAGS -pg"])
+        ESO_PROG_CC_FLAG([g], [CFLAGS="$CFLAGS -g"])
+        ESO_PROG_CC_FLAG([static-libgcc], [CFLAGS="$CFLAGS -static-libgcc"])
+
+        AC_ENABLE_SHARED(no)
+        AC_ENABLE_STATIC(yes)
+    fi
+])
+
+
+# ESO_CHECK_DOCTOOLS
+#-------------------
+AC_DEFUN([ESO_CHECK_DOCTOOLS],
+[
+    AC_ARG_VAR([DOXYGEN], [doxygen command])
+    AC_PATH_PROG([DOXYGEN], [doxygen])
+
+    AC_ARG_VAR([LATEX], [latex command])
+    AC_PATH_PROG([LATEX], [latex])
+
+
+    if test -z "${DOXYGEN}"; then
+        DOXYGEN=":"
+    fi
+
+    if test -z "${LATEX}"; then
+        LATEX=":"
+    fi
+
+])
+
+
+# ESO_PROG_AR
+#------------
+# Checks if ar is in the path
+AC_DEFUN([ESO_PROG_AR],
+[
+    AC_CHECK_PROG(AR, ar, ar, NONE)
+
+    if test x"$AR" = xNONE; then
+        AC_MSG_ERROR([Cannot find \'ar\'])
+    fi
+
+])
+
+
+# ESO_CHECK_EXTRA_LIBS
+#---------------------
+# Check for non-standard headers and libraries
+AC_DEFUN([ESO_CHECK_EXTRA_LIBS],
+[
+
+    AC_ARG_WITH(extra-includes,
+                AC_HELP_STRING([--with-extra-includes=DIR],
+                               [adds non standard include paths]),
+                eso_with_extra_includes=$withval, eso_with_extra_includes=NONE)
+
+    AC_ARG_WITH(extra-libs,
+                AC_HELP_STRING([--with-extra-libs=DIR],
+                              [adds non standard library paths]),
+                eso_with_extra_libs=$withval, eso_with_extra_libs=NONE)
+
+    AC_MSG_CHECKING([for extra includes])
+    AC_CACHE_VAL([eso_cv_with_extra_includes],
+                 [
+                     eso_cv_with_extra_includes=$eso_with_extra_includes
+                 ])
+
+    if test x"$eso_cv_with_extra_includes" != xNONE; then
+        eso_save_IFS=$IFS
+        IFS=':'
+
+        for dir in $eso_cv_with_extra_includes; do
+            EXTRA_INCLUDES="$EXTRA_INCLUDES -I$dir"
+        done
+
+        IFS=$eso_save_IFS
+        AC_MSG_RESULT(added)
+    else
+        AC_MSG_RESULT(no)
+    fi
+
+
+    AC_MSG_CHECKING([for extra libs])
+    AC_CACHE_VAL([eso_cv_with_extra_libs],
+                 [
+                     eso_cv_with_extra_libs=$eso_with_extra_libs
+                 ])
+
+    if test x"$eso_cv_with_extra_libs" != xNONE; then
+        eso_save_IFS=$IFS
+        IFS=':'
+
+        for dir in $eso_cv_with_extra_libs; do
+            EXTRA_LDFLAGS="$EXTRA_LDFLAGS -L$dir"
+        done
+
+        IFS=$eso_save_IFS
+        AC_MSG_RESULT(added)
+    else
+        AC_MSG_RESULT(no)
+    fi
+
+])
+
+
+# ESO_CHECK_THREADS_POSIX
+#------------------------
+# Check whether the POSIX threads are available. The cached result is
+# set to 'yes' if either the compiler supports the '-pthread' flag, or linking
+# with the POSIX thread library works, and the header file defining the POSIX
+# threads symbols is present. If POSIX threads are not supported, the
+# result is set to 'no'. Whether the compiler supports POSIX threads,
+# or whether the library, and the header are available is stored in cache
+# variables.  
+AC_DEFUN([ESO_CHECK_THREADS_POSIX],
+[
+    AC_REQUIRE([AC_PROG_CC])
+
+    ESO_PROG_CC_FLAG([pthread], [], [])
+    
+    AC_CHECK_LIB([pthread], [pthread_create],
+                 [eso_threads_have_libpthread=yes],
+                 [eso_threads_have_libpthread=no])
+
+    AC_CHECK_HEADER([pthread.h],
+                    [eso_threads_have_pthread_h=yes],
+                    [eso_threads_have_pthread_h=no])
+
+    if test x"$eso_threads_have_pthread_h" != xyes; then
+        eso_threads_posix=no
+    else
+        if test x"$eso_threads_have_libpthread" != xyes && \
+          test x"$eso_cv_prog_cc_pthread" != xyes; then
+            eso_threads_posix=no
+        else
+            eso_threads_posix=yes
+        fi
+    fi
+    
+    
+    # Setup the POSIX thread symbols
+
+    if test x"$eso_threads_have_pthread_h" = xyes; then
+        AC_DEFINE([HAVE_PTHREAD_H], [1],
+                  [Define to 1 if you have <pthread.h>.])
+    fi
+    
+    if test x"$eso_threads_posix" = xyes; then
+    
+        if test x"$eso_cv_prog_cc_pthread" = xyes; then
+            PTHREAD_CFLAGS="-pthread"
+        else
+            PTHREAD_CFLAGS=""
+        fi
+        
+        if test x"$eso_threads_have_libpthread" = xyes; then
+            LIBPTHREAD="-lpthread"
+        else
+            LIBPTHREAD=""
+        fi
+        
+    fi  
+
+    AC_CACHE_VAL(eso_cv_threads_posix_header,
+                 eso_cv_threads_posix_header=$eso_threads_have_pthread_h)          
+    AC_CACHE_VAL(eso_cv_threads_posix_lib,
+                 eso_cv_threads_posix_lib=$eso_threads_have_libpthread)          
+    AC_CACHE_VAL(eso_cv_threads_posix_flags,
+                 eso_cv_threads_posix_flags=$eso_cv_prog_cc_pthread)          
+    AC_CACHE_VAL(eso_cv_threads_posix,
+                 eso_cv_threads_posix=$eso_threads_posix)
+
+    AC_SUBST(PTHREAD_CFLAGS)
+    AC_SUBST(LIBPTHREAD)
+    
+])
+
+
+# ESO_CHECK_FUNC(FUNCTION, INCLUDES, SYMBOL)
+#-------------------------------------------
+# Checks whether a function is available and declared.
+AC_DEFUN([ESO_CHECK_FUNC],
+[
+
+    AC_LANG_PUSH(C)
+
+    AC_CHECK_DECL($1, [], [], [$2])
+
+    eso_save_CFLAGS="$CFLAGS"
+
+    if test x"$GCC" = xyes; then
+        CFLAGS="$CFLAGS -pedantic-errors"
+    fi
+
+    AC_CHECK_FUNC($1)
+
+    CFLAGS="$eso_save_CFLAGS"
+
+    AC_LANG_POP(C)
+
+    if test x"$ac_cv_have_decl_$1" = xyes &&
+       test x"$ac_cv_func_$1" = xyes; then
+        AC_DEFINE($3)
+    fi
+
+])
+
+
+# ESO_FUNC_VSNPRINTF_C99
+#-----------------------
+# Check whether vsnprintf() has C99 semantics.
+AC_DEFUN([ESO_FUNC_VSNPRINTF_C99],
+[
+
+    AH_TEMPLATE([HAVE_VSNPRINTF_C99],
+                [Define if you have the C99 `vsnprintf' function.])
+
+    AC_CACHE_CHECK([whether vsnprintf has C99 semantics],
+                   [eso_cv_func_vsnprintf_c99],
+                   [
+                       AC_LANG_PUSH(C)
+
+                       eso_cppflags_save="$CPPFLAGS"
+                       eso_cflags_save="$CFLAGS"
+                       eso_ldflags_save="$LDFLAGS"
+                       eso_libs_save="$LIBS"
+
+                      if test x$GCC = xyes; then
+                          CFLAGS="$CFLAGS -pedantic-errors"
+                          CPPFLAGS="$CPPFLAGS $CFLAGS"
+                      fi
+
+                       AC_RUN_IFELSE([[
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+int
+doit(char * s, ...)
+{
+    char buffer[32];
+    va_list args;
+    int q, r;
+
+    va_start(args, s);
+    q = vsnprintf(NULL, 0, s, args);
+    r = vsnprintf(buffer, 5, s, args);
+    va_end(args);
+
+    if (q != 7 || r != 7)
+      exit(1);
+
+    exit(0);
+}
+
+int
+main(void)
+{
+    doit((char*)"1234567");
+    exit(1);
+}
+                                     ]],
+                                     eso_cv_func_vsnprintf_c99=yes,
+                                     eso_cv_func_vsnprintf_c99=no,
+                                     eso_cv_func_vsnprintf_c99=no)
+
+                       CPPFLAGS="$eso_cppflags_save"
+                       CFLAGS="$eso_cflags_save"
+                       LDFLAGS="$eso_ldflags_save"
+                       LIBS="$eso_libs_save"
+
+                       AC_LANG_POP(C)
+                   ])
+
+# Note that the default is to be pessimistic in the case of cross compilation.
+# If you know that the target has a C99 vsnprintf(), you can get around this
+# by setting eso_func_vsnprintf_c99 to yes, as described in the Autoconf
+# manual.
+
+    if test x$eso_cv_func_vsnprintf_c99 = xyes; then
+        AC_DEFINE(HAVE_VSNPRINTF_C99)
+    fi
+
+])
+
+
+# ESO_CHECK_PRINTF_FORMATS
+#-------------------------
+# Checks for printf() format peculiarities.
+AC_DEFUN([ESO_CHECK_PRINTF_FORMATS],
+[
+
+    # Check if string format for NULL is `(null)'
+
+    AH_TEMPLATE([HAVE_PRINTF_STR_FMT_NULL],
+                [Define if printf outputs `(null)' when printing NULL using
+                 `%s'])
+
+    AC_RUN_IFELSE([[
+#include <stdio.h>
+#include <string.h>
+
+int main()
+{
+    char s[128];
+
+    sprintf(s, "%s", NULL);
+    return strncmp(s, "(null)", 6) ? 1 : 0;
+}
+                  ]],
+                  eso_have_printf_str_format_null=yes,
+                  eso_have_printf_str_format_null=no,
+                  eso_have_printf_str_format_null=no
+                 )
+
+    if test x$eso_have_printf_str_format_null = xyes; then
+        AC_DEFINE(HAVE_PRINTF_STR_FMT_NULL)
+    fi
+
+
+    # Check if pointer format for NULL is `(nil)'
+
+    AH_TEMPLATE([HAVE_PRINTF_PTR_FMT_NIL],
+                [Define if printf outputs `(nil)' when printing NULL using
+                 `%p'])
+
+    AC_RUN_IFELSE([[
+#include <stdio.h>
+#include <string.h>
+
+int main()
+{
+    char s[128];
+
+    sprintf(s, "%p", NULL);
+    return strncmp(s, "(nil)", 5) ? 1 : 0;
+}
+                  ]],
+                  eso_have_printf_ptr_format_nil=yes,
+                  eso_have_printf_ptr_format_nil=no,
+                  eso_have_printf_ptr_format_nil=no
+                 )
+
+    if test x$eso_have_printf_ptr_format_nil = xyes; then
+        AC_DEFINE(HAVE_PRINTF_PTR_FMT_NIL)
+    fi
+
+
+    # Check if output for `%p' is the same as `%#x'
+
+    AH_TEMPLATE([HAVE_PRINTF_PTR_FMT_ALTERNATE],
+                [Define if printf format `%p' produces the same output as
+                 `%#x' or `%#lx'])
+
+    AC_RUN_IFELSE([[
+#include <stdio.h>
+#include <string.h>
+
+int main()
+{
+    char s1[128], s2[128];
+
+    sprintf(s1, "%p", s1);
+    sprintf(s2, "%#x", s1);
+    return strncmp(s1, s2, 3) ? 1 : 0;
+}
+                  ]],
+                  eso_have_printf_ptr_format_alternate=yes,
+                  eso_have_printf_ptr_format_alternate=no,
+                  eso_have_printf_ptr_format_alternate=no
+                 )
+
+    if test x$eso_have_printf_ptr_format_alternate = xyes; then
+        AC_DEFINE(HAVE_PRINTF_PTR_FMT_ALTERNATE)
+    fi
+
+
+    # Check if pointers are treated as signed
+
+    AH_TEMPLATE([HAVE_PRINTF_PTR_FMT_SIGNED],
+                [Define if printf treats pointers as signed when using a sign
+                 flag])
+
+    AC_RUN_IFELSE([[
+#include <stdio.h>
+
+int main()
+{
+    char s[128];
+
+    sprintf(s, "%+p", s);
+    return s[0] == '+' ? 0 : 1;
+}
+                  ]],
+                  eso_have_printf_ptr_format_signed=yes,
+                  eso_have_printf_ptr_format_signed=no,
+                  eso_have_printf_ptr_format_signed=no
+                 )
+
+    if test x$eso_have_printf_ptr_format_signed = xyes; then
+        AC_DEFINE(HAVE_PRINTF_PTR_FMT_SIGNED)
+    fi
+
+
+    # Check if default precision for conversion specifier `g' is 1 (as
+    # required by ISO C) or 6.
+
+    AH_TEMPLATE([HAVE_PRINTF_FLT_FMT_G_STD],
+                [Define if printf default precision for format `g' is 1
+                 (ISO C standard) or 6])
+
+    AC_RUN_IFELSE([[
+#include <stdio.h>
+
+int main()
+{
+    char s1[128], s2[128];
+    int n1, n2;
+
+    sprintf(s1, "%g%n", 1.123456, &n1);
+    sprintf(s2, "%.1g%n", 1.123456, &n2);
+    return n1 > n2 ? 1 : 0;
+}
+                  ]],
+                  eso_have_printf_flt_format_g_std=yes,
+                  eso_have_printf_flt_format_g_std=no,
+                  eso_have_printf_flt_format_g_std=no
+                 )
+
+    if test x$eso_have_printf_flt_format_g_std = xyes; then
+        AC_DEFINE(HAVE_PRINTF_FLT_FMT_G_STD)
+    fi
+
+])
+
+
+# ESO_FUNC_VSNPRINTF
+#-------------------
+# Checks for vsnprintf and snprintf declaration and function.
+AC_DEFUN([ESO_FUNC_VSNPRINTF],
+[
+
+    eso_compile_snprintf=no
+
+    AH_TEMPLATE([HAVE_VSNPRINTF],
+                [Define if you have the `vsnprintf' function])
+    ESO_CHECK_FUNC(vsnprintf, [
+#include <stdio.h>
+#include <stdarg.h>
+                              ], HAVE_VSNPRINTF)
+
+    if test x$ac_cv_func_vsnprintf = xyes &&
+       test x$ac_cv_have_decl_vsnprintf = xyes; then
+
+        ESO_FUNC_VSNPRINTF_C99
+
+        if test x$eso_cv_func_vsnprintf_c99 != xyes; then
+            eso_compile_snprintf=yes
+        fi
+
+    else
+        eso_compile_snprintf=yes
+    fi
+
+    if test x$eso_compile_snprintf = xyes; then
+        if test -n "$LIBTOOL"; then
+            SNPRINTF=snprintf.lo
+        else
+            SNPRINTF=snprintf.$ac_objext
+        fi
+    fi
+
+    AC_SUBST(SNPRINTF)
+
+    # The symbols defined by the following macro are only needed to setup the
+    # vsnprintf() replacement. May be useless if the vsnprintf implementation
+    # changes.
+    ESO_CHECK_PRINTF_FORMATS
+
+    AH_TEMPLATE([HAVE_SNPRINTF],
+                [Define if you have the `snprintf' function])
+    ESO_CHECK_FUNC(snprintf, [#include <stdio.h>], HAVE_SNPRINTF)
+
+])
+
+
+# ESO_FUNC_VASPRINTF
+#-------------------
+# Checks for vasprintf declaration and function.
+AC_DEFUN([ESO_FUNC_VASPRINTF],
+[
+
+    AH_TEMPLATE([HAVE_VASPRINTF],
+                [Define if you have the `vasprintf' function])
+    ESO_CHECK_FUNC(vasprintf, [
+#include <stdio.h>
+#include <stdarg.h>
+                              ], HAVE_VASPRINTF)
+
+])
+
+
+# ESO_FUNC_FPATHCONF
+#-------------------
+# Checks for fpathconf declaration and function.
+AC_DEFUN([ESO_FUNC_FPATHCONF],
+[
+
+    AH_TEMPLATE([HAVE_FPATHCONF],
+                [Define if you have the `fpathconf' function])
+    ESO_CHECK_FUNC(fpathconf, [#include <unistd.h>], HAVE_FPATHCONF)
+
+    # If we have fpathconf we should also have pathconf, but who knows.
+    AH_TEMPLATE([HAVE_PATHCONF],
+                [Define if you have the `pathconf' function])
+    ESO_CHECK_FUNC(pathconf, [#include <unistd.h>], HAVE_PATHCONF)
+
+])
+
+
+# ESO_FUNC_SYSCONF
+#-----------------
+# Checks for sysconf declaration and function.
+AC_DEFUN([ESO_FUNC_SYSCONF],
+[
+
+    AH_TEMPLATE([HAVE_SYSCONF],
+                [Define if you have the `sysconf' function])
+    ESO_CHECK_FUNC(sysconf, [#include <unistd.h>], HAVE_SYSCONF)
+
+])
+
+
+# ESO_FUNC_GETOPT
+#----------------
+# Checks for GNU getopt_long declaration and function.
+AC_DEFUN([ESO_FUNC_GETOPT],
+[
+
+    AH_TEMPLATE([HAVE_GETOPT_LONG],
+                [Define if you have the `getopt_long' function])
+
+    ESO_CHECK_FUNC(getopt_long, [#include <getopt.h>], HAVE_GETOPT_LONG)
+
+    if test x"$ac_cv_func_getopt_long" = xno ||
+       test x"$eso_cv_have_decl_getopt_long" = xno; then
+        if test -n "$LIBTOOL"; then
+            GETOPT="getopt.lo getopt1.lo"
+        else
+            GETOPT="getopt.$ac_objext getopt1.$ac_objext"
+        fi
+    fi
+
+    AC_SUBST(GETOPT)
+
+
+])
+
+
+# ESO_FUNC_GETPWUID
+#------------------
+# Checks for getpwuid declaration and function.
+AC_DEFUN([ESO_FUNC_GETPWUID],
+[
+
+    AH_TEMPLATE([HAVE_GETPWUID],
+                [Define if you have the `getpwuid' function])
+
+    ESO_CHECK_FUNC(getpwuid, [#include <pwd.h>], HAVE_GETPWUID)
+
+])
+
+
+# ESO_FUNC_GETUID
+#----------------
+AC_DEFUN([ESO_FUNC_GETUID],
+[
+
+    AH_TEMPLATE([HAVE_GETUID],
+                [Define if you have the `getuid' function])
+
+    ESO_CHECK_FUNC(getuid, [#include <unistd.h>], HAVE_GETUID)
+
+])
+
+
+# ESO_FUNC_LSTAT
+#---------------
+AC_DEFUN([ESO_FUNC_LSTAT],
+[
+
+    AH_TEMPLATE([HAVE_LSTAT],
+                [Define if you have the `lstat' function])
+
+    ESO_CHECK_FUNC(lstat, [#include <sys/stat.h>], HAVE_LSTAT)
+
+])
+
+
+# ESO_FUNC_STRDUP
+#----------------
+AC_DEFUN([ESO_FUNC_STRDUP],
+[
+
+    AH_TEMPLATE([HAVE_STRDUP],
+                [Define if you have the `strdup' function])
+
+    ESO_CHECK_FUNC(strdup, [#include <string.h>], HAVE_STRDUP)
+
+    AH_BOTTOM([
+#ifndef HAVE_STRDUP
+#  define strdup  cx_strdup
+#endif
+              ])
+])
+
+
+# ESO_FUNC_STPCPY
+#----------------
+AC_DEFUN([ESO_FUNC_STPCPY],
+[
+
+    AH_TEMPLATE([HAVE_STPCPY],
+                [Define if you have the `stpcpy' function])
+
+    ESO_CHECK_FUNC(stpcpy, [#include <stpcpy.h>], HAVE_STPCPY)
+
+])
+
+
+# ESO_FUNC_SYMLINK
+#-----------------
+AC_DEFUN([ESO_FUNC_SYMLINK],
+[
+
+    AH_TEMPLATE([HAVE_SYMLINK],
+                [Define if you have the `symlink' function])
+
+    ESO_CHECK_FUNC(symlink, [#include <unistd.h>], HAVE_SYMLINK)
+
+])
+
+
+# ESO_FUNC_WORDEXP
+#-----------------
+AC_DEFUN([ESO_FUNC_WORDEXP],
+[
+
+    AH_TEMPLATE([HAVE_WORDEXP],
+                [Define if you have the `wordexp' function])
+
+    ESO_CHECK_FUNC(wordexp, [#include <wordexp.h>], HAVE_WORDEXP)
+
+])
+
+
+# ESO_FUNC_GETTIMEOFDAY
+#----------------------
+AC_DEFUN([ESO_FUNC_GETTIMEOFDAY],
+[
+
+    AH_TEMPLATE([HAVE_GETTIMEOFDAY],
+                [Define if you have the `gettimeofday' function])
+
+    ESO_CHECK_FUNC(gettimeofday,
+                   [
+                       #include <unistd.h>
+                       #include <sys/time.h>
+                   ],
+                   HAVE_GETTIMEOFDAY)
+])
+
+
+# ESO_FUNC_VA_COPY(symbol)
+#-------------------------
+# Check for an implementation of va_copy(). The argument which must be
+# given is the preprocessor symbol that is defined to be either va_copy
+# or __va_copy depending on the available function, provided that an
+# implementation of va_copy is available at all.
+AC_DEFUN([ESO_FUNC_VA_COPY],
+[
+
+    # Check for all three va_copy possibilities, so we get
+    # all results in config.log for bug reports.
+
+    # Check for availability of va_copy(). This is ISO C. Available with
+    # gcc since version 3.0.
+    AC_CACHE_CHECK([for an implementation of va_copy()], eso_cv_have_va_copy,
+                   [
+                       AC_RUN_IFELSE([
+#include <stdarg.h>
+
+void f(int i, ...)
+{
+    va_list args1, args2;
+    va_start (args1, i);
+    va_copy (args2, args1);
+
+    if (va_arg (args2, int) != 42 || va_arg (args1, int) != 42)
+        exit (1);
+
+    va_end (args1);
+    va_end (args2);
+}
+
+int main() {
+  f(0, 42);
+  return 0;
+}
+                                     ],
+                                     eso_cv_have_va_copy=yes,
+                                     eso_cv_have_va_copy=no,
+                                     eso_cv_have_va_copy=no)
+                   ])
+
+
+    # Check for availability of __va_copy(). Some compilers provide
+    # this. Available with gcc since version 2.8.1.
+    AC_CACHE_CHECK([for an implementation of __va_copy()],
+                   eso_cv_have__va_copy,
+                   [
+                       AC_RUN_IFELSE([
+#include <stdarg.h>
+
+void f(int i, ...)
+{
+    va_list args1, args2;
+
+    va_start (args1, i);
+    __va_copy (args2, args1);
+
+    if (va_arg (args2, int) != 42 || va_arg (args1, int) != 42)
+        exit (1);
+
+    va_end (args1);
+    va_end (args2);
+}
+
+int main()
+{
+    f(0, 42);
+    return 0;
+}
+                                     ],
+                                     eso_cv_have__va_copy=yes,
+                                     eso_cv_have__va_copy=no,
+                                     eso_cv_have__va_copy=no)
+
+                   ])
+
+    if test "x$eso_cv_have_va_copy" = "xyes"; then
+        eso_func_va_copy=va_copy
+    else
+        if test "x$eso_cv_have__va_copy" = "xyes"; then
+            eso_func_va_copy=__va_copy
+        fi
+    fi
+
+    AH_TEMPLATE([HAVE_VA_COPY],
+                [Define if you have an implementation of a `va_copy()' style
+                 function.])
+    AH_TEMPLATE([$1], [A `va_copy()' style function])
+
+    if test -n "$eso_func_va_copy"; then
+        AC_DEFINE_UNQUOTED([$1], $eso_func_va_copy)
+        AC_DEFINE(HAVE_VA_COPY)
+    fi
+
+    # Check whether va_lists can be copied by value
+    AC_CACHE_CHECK([whether va_lists can be copied by value],
+                   eso_cv_have_va_value_copy,
+                   [
+                       AC_RUN_IFELSE([
+#include <stdarg.h>
+
+void f(int i, ...)
+{
+    va_list args1, args2;
+    va_start (args1, i);
+    args2 = args1;
+
+    if (va_arg (args2, int) != 42 || va_arg (args1, int) != 42)
+        exit (1);
+
+    va_end (args1);
+    va_end (args2);
+}
+
+int main()
+{
+  f(0, 42);
+  return 0;
+}
+                                     ],
+                                     eso_cv_have_va_value_copy=yes,
+                                     eso_cv_have_va_val_copy=no,
+                                     eso_cv_have_va_val_copy=no)
+                   ])
+
+    AH_TEMPLATE([HAVE_VA_LIST_COPY_BY_VALUE],
+                [Define if `va_lists' can be copied by value])
+    if test "x$eso_cv_have_va_value_copy" = "xyes"; then
+        AC_DEFINE(HAVE_VA_LIST_COPY_BY_VALUE)
+    fi
+
+])
+
+
+# ESO_FUNC_REALLOC_SANITY
+#-------------------------
+# Check whether realloc(NULL,) works.
+AC_DEFUN([ESO_FUNC_REALLOC_SANITY],
+[
+    AC_CACHE_CHECK([whether realloc(NULL,) works],
+                   eso_cv_have_sane_realloc,
+                   [
+                       AC_RUN_IFELSE([
+#include <stdlib.h>
+
+int main()
+{
+    return realloc (0, sizeof (int)) == 0;
+}
+                                     ],
+                                     eso_cv_have_sane_realloc=yes,
+                                     eso_cv_have_sane_realloc=no,
+                                     eso_cv_have_sane_realloc=no)
+                   ])
+
+    AH_TEMPLATE([HAVE_WORKING_REALLOC],
+                [Define if realloc(NULL,) works])
+
+    if test x$eso_cv_have_sane_realloc = xyes; then
+        AC_DEFINE(HAVE_WORKING_REALLOC)
+    fi
+
+])
+
+
+# ESO_FIND_FILE(file, directories, variable)
+#------------------------------------------
+# Search for file in directories. Set variable to the first location
+# where file was found, if file is not found at all variable is set to NO.
+AC_DEFUN([ESO_FIND_FILE],
+[
+    $3=no
+
+    for i in $2; do
+        for j in $1; do
+
+            echo "configure: __oline__: $i/$j" >&AC_FD_CC
+
+            if test -r "$i/$j"; then
+                echo "taking that" >&AC_FD_CC
+                $3=$i
+                break 2
+            fi
+        done
+    done
+])
+
+
+# ESO_SET_LIBRARY_VERSION([CURRENT], [REVISION], [AGE])
+#------------------------------------------------------
+# Sets the libtool versioning symbols LT_CURRENT, LT_REVISION, LT_AGE.
+AC_DEFUN([ESO_SET_LIBRARY_VERSION],
+[
+
+    if test -z "$1"; then
+        LT_CURRENT=0
+    else
+        LT_CURRENT="$1"
+    fi
+
+    if test -z "$2"; then
+        LT_REVISION=0
+    else
+        LT_REVISION="$2"
+    fi
+
+    if test -z "$3"; then
+        LT_AGE=0
+    else
+        LT_AGE="$3"
+    fi
+
+    AC_SUBST(LT_CURRENT)
+    AC_SUBST(LT_REVISION)
+    AC_SUBST(LT_AGE)
+])
diff --git a/test/iiinstrumentp/recipes/Makefile.am b/test/iiinstrumentp/recipes/Makefile.am
new file mode 100644
index 0000000..1186b4d
--- /dev/null
+++ b/test/iiinstrumentp/recipes/Makefile.am
@@ -0,0 +1,33 @@
+## Process this file with automake to produce Makefile.in
+
+##   This file is part of the IIINSTRUMENT Pipeline
+##   Copyright (C) 2002,2003 European Southern Observatory
+
+##   This library 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+AUTOMAKE_OPTIONS = 1.8 foreign
+
+DISTCLEANFILES = *~
+
+INCLUDES = $(all_includes)
+
+noinst_HEADERS = 
+
+plugin_LTLIBRARIES = rtest.la 
+
+rtest_la_SOURCES = rtest.c 
+rtest_la_LIBADD = $(LIBCPLDFS) $(LIBIIINSTRUMENT)
+rtest_la_LDFLAGS = -module -avoid-version
+rtest_la_DEPENDENCIES = $(LIBIIINSTRUMENT)
diff --git a/test/iiinstrumentp/recipes/rtest.c b/test/iiinstrumentp/recipes/rtest.c
new file mode 100644
index 0000000..21d0ca3
--- /dev/null
+++ b/test/iiinstrumentp/recipes/rtest.c
@@ -0,0 +1,463 @@
+/* $Id: rtest.c,v 1.28 2007/07/30 07:08:59 llundin Exp $
+ *
+ * This file is part of the IIINSTRUMENT Pipeline
+ * Copyright (C) 2002,2003 European Southern Observatory
+ * Copyright (C) 2011,2012 Ole Streicher
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+/*-----------------------------------------------------------------------------
+				Includes
+ -----------------------------------------------------------------------------*/
+
+#include <cpl.h>
+
+#include "iiinstrument_utils.h"
+#include "iiinstrument_pfits.h"
+#include "iiinstrument_dfs.h"
+
+/*-----------------------------------------------------------------------------
+			    Private function prototypes
+ -----------------------------------------------------------------------------*/
+
+static int rtest_create(cpl_plugin *);
+static int rtest_exec(cpl_plugin *);
+static int rtest_destroy(cpl_plugin *);
+static int rtest(cpl_frameset *, const cpl_parameterlist *);
+
+/*-----------------------------------------------------------------------------
+			    Static variables
+ -----------------------------------------------------------------------------*/
+
+static char rtest_description[] =
+"Recipe to test CPL frameworks like esorex or python-cpl.\n";
+
+/*-----------------------------------------------------------------------------
+				Function code
+ -----------------------------------------------------------------------------*/
+
+/*----------------------------------------------------------------------------*/
+/**
+  @brief    Build the list of available plugins, for this module.
+  @param    list    the plugin list
+  @return   0 if everything is ok, 1 otherwise
+  @note     Only this function is exported
+
+  Create the recipe instance and make it available to the application using the
+  interface.
+ */
+/*----------------------------------------------------------------------------*/
+int cpl_plugin_get_info(cpl_pluginlist * list)
+{
+    cpl_recipe  *   recipe = cpl_calloc(1, sizeof *recipe );
+    cpl_plugin  *   plugin = &recipe->interface;
+
+    if (cpl_plugin_init(plugin,
+		    CPL_PLUGIN_API,
+		    IIINSTRUMENT_BINARY_VERSION,
+		    CPL_PLUGIN_TYPE_RECIPE,
+		    "rtest",
+		    "Framework test recipe",
+		    rtest_description,
+		    "Ole Streicher",
+		    "python-cpl at liska.ath.cx",
+		    iiinstrument_get_license(),
+		    rtest_create,
+		    rtest_exec,
+		    rtest_destroy)) {
+	cpl_msg_error(cpl_func, "Plugin initialization failed");
+	(void)cpl_error_set_where(cpl_func);
+	return 1;
+    }
+
+    if (cpl_pluginlist_append(list, plugin)) {
+	cpl_msg_error(cpl_func, "Error adding plugin to list");
+	(void)cpl_error_set_where(cpl_func);
+	return 1;
+    }
+
+    return 0;
+}
+
+/*----------------------------------------------------------------------------*/
+/**
+  @brief    Setup the recipe options
+  @param    plugin  the plugin
+  @return   0 if everything is ok
+
+  Defining the command-line/configuration parameters for the recipe.
+ */
+/*----------------------------------------------------------------------------*/
+static int rtest_create(cpl_plugin * plugin)
+{
+    cpl_ensure_code((plugin != NULL), CPL_ERROR_NULL_INPUT);
+    cpl_ensure_code((cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE),
+		    CPL_ERROR_TYPE_MISMATCH);
+
+    cpl_recipe *recipe = (cpl_recipe *)plugin;
+
+    /* Create the parameters list in the cpl_recipe object */
+    recipe->parameters = cpl_parameterlist_new();
+    if (recipe->parameters == NULL) {
+	cpl_msg_error(cpl_func, "Parameter list allocation failed");
+	cpl_ensure_code(0, (int)CPL_ERROR_ILLEGAL_OUTPUT);
+    }
+
+    /* Fill the parameters list */
+    cpl_parameter * p;
+    /* --stropt */
+    p = cpl_parameter_new_value("iiinstrument.rtest.string_option",
+	    CPL_TYPE_STRING,
+	    "A string option; saved as ESO QC STROPT",
+	    "iiinstrument.rtest",NULL);
+    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "stropt");
+    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
+    cpl_parameterlist_append(recipe->parameters, p);
+
+    /* --boolopt */
+    p = cpl_parameter_new_value("iiinstrument.rtest.bool_option",
+	    CPL_TYPE_BOOL,
+	    "A flag; saved as ESO QC BOOLOPT",
+	    "iiinstrument.rtest", TRUE);
+    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "boolopt");
+    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
+    cpl_parameterlist_append(recipe->parameters, p);
+
+    /* --floatopt */
+    p = cpl_parameter_new_value("iiinstrument.rtest.float_option",
+	    CPL_TYPE_DOUBLE,
+	    "A double option; saved as ESO QC FLOATOPT",
+	    "iiinstrument.rtest", 0.1);
+    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "floatopt");
+    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
+    cpl_parameterlist_append(recipe->parameters, p);
+
+    /* --inttopt */
+    p = cpl_parameter_new_value("iiinstrument.rtest.int_option",
+	    CPL_TYPE_INT,
+	    "An interger; saved as ESO QC INTOPT",
+	    "iiinstrument.rtest", 2);
+    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "intopt");
+    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
+    cpl_parameterlist_append(recipe->parameters, p);
+
+    /* --enumopt */
+    p = cpl_parameter_new_enum("iiinstrument.rtest.enum_option",
+	    CPL_TYPE_STRING,
+	    "An enumeration option, saved as ESO QC ENUMOPT",
+	    "iiinstrument.rtest", "first", 3, "first", "second", "third");
+    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "enumopt");
+    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
+    cpl_parameterlist_append(recipe->parameters, p);
+
+    /* --rangeopt */
+    p = cpl_parameter_new_range("iiinstrument.rtest.range_option",
+	    CPL_TYPE_DOUBLE,
+	    "A double option with a range, saved as ESO QC RANGEOPT",
+	    "iiinstrument.rtest", 0.1, -0.5, 0.5);
+    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "rangeopt");
+    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
+    cpl_parameterlist_append(recipe->parameters, p);
+
+    /* --dot.opt */
+    p = cpl_parameter_new_value("iiinstrument.rtest.dotted.opt",
+	    CPL_TYPE_INT,
+	    "An (integer) option with a dot in its name",
+	    "iiinstrument.rtest", 0);
+    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "dot.opt");
+    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
+    cpl_parameterlist_append(recipe->parameters, p);
+
+    /* --crashing */
+    p = cpl_parameter_new_enum("iiinstrument.rtest.crashing",
+	  CPL_TYPE_STRING, "Crash the recipe?", "iiinstrument.rtest",
+			       "no", 3, "no", "free", "segfault");
+    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "crashing");
+    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
+    cpl_parameterlist_append(recipe->parameters, p);
+
+    /* --memleak */
+    p = cpl_parameter_new_value("iiinstrument.rtest.memleak",
+	    CPL_TYPE_BOOL,
+	    "If yes, dont deallocate some memory",
+	    "iiinstrument.rtest", FALSE);
+    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "memleak");
+    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
+    cpl_parameterlist_append(recipe->parameters, p);
+
+    /* --sleep */
+    p = cpl_parameter_new_value("iiinstrument.rtest.sleep",
+	    CPL_TYPE_DOUBLE,
+	    "Simulate some computing by sleeping for specified time [seconds]",
+	    "iiinstrument.rtest", 0.1);
+    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "sleep");
+    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
+    cpl_parameterlist_append(recipe->parameters, p);
+
+    /* --disabled */
+    p = cpl_parameter_new_value("iiinstrument.rtest.disabled",
+	    CPL_TYPE_DOUBLE,
+	    "Dummy disabled parameter",
+	    "iiinstrument.rtest", -0.1);
+    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, "disabled");
+    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
+    cpl_parameter_disable(p, CPL_PARAMETER_MODE_CLI);
+    cpl_parameterlist_append(recipe->parameters, p);
+
+    return 0;
+}
+
+/*----------------------------------------------------------------------------*/
+/**
+  @brief    Execute the plugin instance given by the interface
+  @param    plugin  the plugin
+  @return   0 if everything is ok
+ */
+/*----------------------------------------------------------------------------*/
+static int rtest_exec(cpl_plugin * plugin)
+{
+    cpl_ensure_code((plugin != NULL), CPL_ERROR_NULL_INPUT);
+    cpl_ensure_code((cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE),
+		    CPL_ERROR_TYPE_MISMATCH);
+
+    cpl_recipe *recipe = (cpl_recipe *)plugin;
+
+    cpl_ensure_code((recipe->parameters != NULL), (int)CPL_ERROR_NULL_INPUT);
+    cpl_ensure_code((recipe->frames != NULL), (int)CPL_ERROR_NULL_INPUT);
+
+    int recipe_status = rtest(recipe->frames, recipe->parameters);
+
+    /* Ensure DFS-compliance of the products */
+    if (cpl_dfs_update_product_header(recipe->frames)) {
+	if (!recipe_status) recipe_status = (int)cpl_error_get_code();
+    }
+
+    return recipe_status;
+}
+
+/*----------------------------------------------------------------------------*/
+/**
+  @brief    Destroy what has been created by the 'create' function
+  @param    plugin  the plugin
+  @return   0 if everything is ok
+ */
+/*----------------------------------------------------------------------------*/
+static int rtest_destroy(cpl_plugin * plugin)
+{
+    cpl_ensure_code((plugin != NULL), CPL_ERROR_NULL_INPUT);
+    cpl_ensure_code((cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE),
+		    CPL_ERROR_TYPE_MISMATCH);
+
+    cpl_recipe *recipe = (cpl_recipe *)plugin;
+    cpl_parameterlist_delete(recipe->parameters);
+
+    return 0;
+}
+
+/*----------------------------------------------------------------------------*/
+/**
+  @brief    Interpret the command line options and execute the data processing
+  @param    frameset   the frames list
+  @param    parlist    the parameters list
+  @return   0 if everything is ok
+ */
+/*----------------------------------------------------------------------------*/
+static int rtest(cpl_frameset            * frameset,
+		    const cpl_parameterlist * parlist)
+{
+    /* Use the errorstate to detect an error in a function that does not
+       return an error code. */
+    cpl_errorstate prestate = cpl_errorstate_get();
+
+    const cpl_parameter *param;
+    /* --stropt */
+    param = cpl_parameterlist_find_const(parlist,
+					 "iiinstrument.rtest.string_option");
+    const char *str_option = cpl_parameter_get_string(param);
+    cpl_ensure_code(str_option != NULL, CPL_ERROR_NULL_INPUT);
+
+    /* --boolopt */
+    param = cpl_parameterlist_find_const(parlist,
+					 "iiinstrument.rtest.bool_option");
+    int bool_option = cpl_parameter_get_bool(param);
+
+    /* --floatopt */
+    param = cpl_parameterlist_find_const(parlist,
+					 "iiinstrument.rtest.float_option");
+    double float_option = cpl_parameter_get_double(param);
+
+    /* --intopt */
+    param = cpl_parameterlist_find_const(parlist,
+					 "iiinstrument.rtest.int_option");
+    int int_option = cpl_parameter_get_int(param);
+
+    /* --enumopt */
+    param = cpl_parameterlist_find_const(parlist,
+					 "iiinstrument.rtest.enum_option");
+    const char *enum_option = cpl_parameter_get_string(param);
+
+    /* --rangeopt */
+    param = cpl_parameterlist_find_const(parlist,
+					 "iiinstrument.rtest.range_option");
+    double range_option = cpl_parameter_get_double(param);
+
+    /* --crashing */
+    param = cpl_parameterlist_find_const(parlist,
+					 "iiinstrument.rtest.crashing");
+    const char *crashing = cpl_parameter_get_string(param);
+
+    /* --memleak */
+    param = cpl_parameterlist_find_const(parlist,
+					 "iiinstrument.rtest.memleak");
+    int memleak = cpl_parameter_get_bool(param);
+
+    /* --sleep */
+    param = cpl_parameterlist_find_const(parlist,
+					 "iiinstrument.rtest.sleep");
+    double sleep_secs = cpl_parameter_get_double(param);
+
+    param = cpl_parameterlist_find_const(parlist,
+					 "iiinstrument.rtest.disabled");
+    double disabled_option = cpl_parameter_get_double(param);
+
+    if (!cpl_errorstate_is_equal(prestate)) {
+	return (int)cpl_error_set_message(cpl_func, cpl_error_get_code(),
+					  "Could not retrieve the input "
+					  "parameters");
+    }
+
+    /* Identify the RAW and CALIB frames in the input frameset */
+    cpl_ensure_code(iiinstrument_dfs_set_groups(frameset) == CPL_ERROR_NONE,
+		    cpl_error_get_code());
+
+    /*  - raw input file */
+    const cpl_frame *rawframe = cpl_frameset_find_const(frameset, RRRECIPE_RAW);
+    if (rawframe == NULL) {
+	/* cpl_frameset_find_const() does not set an error code, when a frame
+	   is not found, so we will set one here. */
+	return (int)cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
+					  "No file tagged with %s", RRRECIPE_RAW);
+    }
+
+    cpl_propertylist *plist
+	= cpl_propertylist_load_regexp(cpl_frame_get_filename(rawframe),
+				       0, "ESO DET ", 0);
+    if (plist == NULL) {
+	/* In this case an error message is added to the error propagation */
+	cpl_msg_error(cpl_func, "Could not read plist from %s",
+		      cpl_frame_get_filename(rawframe));
+	return (int)cpl_error_set_message(cpl_func, cpl_error_get_code(),
+					  "Could not read the FITS header");
+    }
+
+    double qc_param = iiinstrument_pfits_get_dit(plist);
+    cpl_errorstate_set(prestate);
+
+    cpl_propertylist_delete(plist);
+
+    /* - calibration input file */
+    const cpl_frame *flat = cpl_frameset_find(frameset,IIINSTRUMENT_CALIB_FLAT);
+    if (flat == NULL) {
+	cpl_msg_warning(cpl_func, "No file tagged with %s",
+			IIINSTRUMENT_CALIB_FLAT);
+    }
+
+    /* Check for a change in the CPL error state */
+    /* - if it did change then propagate the error and return */
+    cpl_ensure_code(cpl_errorstate_is_equal(prestate), cpl_error_get_code());
+
+    /* Load raw image */
+    cpl_image *image = cpl_image_load(cpl_frame_get_filename(rawframe),
+				      CPL_TYPE_FLOAT, 0, 0);
+
+    /* A multiline debug message */
+    cpl_msg_info(cpl_func, "multiline#1\nmultiline#2\nmultiline#3");
+
+    /* Do some fake processing */
+    usleep((unsigned int)(1e6*sleep_secs));
+
+    /* Add QC parameters  */
+    cpl_propertylist *qclist = cpl_propertylist_new();
+
+    cpl_propertylist_append_double(qclist, "ESO QC QCPARAM", qc_param);
+    cpl_propertylist_append_string(qclist, "ESO PRO CATG","THE_PRO_CATG_VALUE");
+    if (str_option != NULL) {
+	cpl_propertylist_append_string(qclist, "ESO QC STROPT", str_option);
+    } else {
+	cpl_propertylist_append_string(qclist, "ESO QC STROPT", "(null)");
+    }
+
+    cpl_propertylist_append_bool(qclist, "ESO QC BOOLOPT", bool_option);
+    cpl_propertylist_append_double(qclist, "ESO QC FLOATOPT", float_option);
+    cpl_propertylist_append_int(qclist, "ESO QC INTOPT", int_option);
+    if (enum_option != NULL) {
+	cpl_propertylist_append_string(qclist, "ESO QC ENUMOPT", enum_option);
+    } else {
+	cpl_propertylist_append_string(qclist, "ESO QC ENUMOPT", "(null)");
+    }
+    cpl_propertylist_append_double(qclist, "ESO QC RANGEOPT", range_option);
+    const char *testenv = getenv("TESTENV");
+    if (testenv != NULL) {
+	cpl_propertylist_append_string(qclist, "ESO QC TESTENV", testenv);
+    } else {
+	cpl_propertylist_append_string(qclist, "ESO QC TESTENV", "(null)");
+    }
+    cpl_propertylist_append_double(qclist, "ESO QC DISABLEDOPT", disabled_option);
+
+    prestate = cpl_errorstate_get();
+
+    if (cpl_dfs_save_image(frameset, NULL, parlist, frameset, NULL, image,
+			   CPL_BPP_IEEE_FLOAT,
+			   "rtest", qclist, NULL,
+			   PACKAGE "/" PACKAGE_VERSION,
+			   "rtest.fits")) {
+	/* Propagate the error */
+	(void)cpl_error_set_where(cpl_func);
+    }
+
+    if (!cpl_errorstate_is_equal(prestate)) {
+	cpl_msg_error(__func__, "in cpl_dfs_save_image()");
+    }
+
+    cpl_image_delete(image);
+    cpl_propertylist_delete(qclist);
+
+    /* Let's see if we can crash the machine by some random code */
+    if (strcmp(crashing, "free") == 0) {
+	cpl_image_delete(image);
+	cpl_propertylist_delete(qclist);
+    }
+    if (strcmp(crashing, "segfault") == 0) {
+	double *crashvar = NULL;
+	*crashvar = 1.99;
+    }
+
+    if (memleak) {
+	cpl_malloc(16);
+    }
+
+    return (int)cpl_error_get_code();
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-astro/packages/python-cpl.git



More information about the debian-science-commits mailing list