
********
pyidlrpc
********

A library to call IDL (Interactive Data Language) from python.  Allows trasparent wrapping of IDL routines and objects as well as arbitrary execution of IDL code.  

*pyidlrpc* requires a licenced installation of IDL and works by connecting with an idlrpc server.  Data is transfered back and forth using the idlrpc API distrbuted with IDL. 

All standard IDL and python/numpy datatypes are supported, and *pyidlrpc* transparently handles translating between equivilent datatypes. This includes translation between IDL structures and python dictionaries.

*pyidlrpc* is hosted at: 
    https://bitbucket.org/amicitas/pyidlrpc

Documentation can be found at:
    http://amicitas.bitbucket.org/pyidlrpc


*pyidlrpc* was written by Novimir A. Pablant, and is released under an MIT licence.


Dependencies
============

- IDL (8.0)
- python (2.7, 3.0 or later)

  * numpy
  * psutil

- cython

*pyidlrpc* requires a licenced installation of IDL.  Currently only IDL 8.0 or later is spported due to the additon of the ``HASH`` and ``LIST`` types.  Older versions may be compatible but are untested.


*pyidlrpc* is currently only supported on linux and OS X.  It should also work fine on windows, but the ``setup.py`` script has not been setup to find the IDL installation directory.  If you want to give this a try on windows, just add the appropriate directories to setup.py. (If it works submit a pull reqeust so that I can update the public package.)


Installation
============


*pyidlrpc* can be installed using the following command::
  
    python setup.py install

For more control over the installation, all standard ``distutils`` commands are available.  If ``setuputils`` is installed, then the extended commands will also work.


If a full installation is not desired, the *pyidlrpc* module can be built in place using::

    python setup.py build_ext --inplace

This will build *pyidlrpc* in place and create the ``pyidlrpc.so`` shared library which can then be imported from python.


.. warning::
    On Mac OS X the setup script will change the installation name of the libidl_rpc.dylib library to @rpath/libidl_rpc.dylib.


examples
========

Here are a few simple examples.  A full set of examples and tutorials is included in the documentation.

starting the idlrpc server
--------------------------

For any of the examples below it is recommended than an ``idlrpc`` server be started in a separate terminal window::

    idlrpc

This step is not strictly necessary as a server will be started automatically if needed. However, *pyidlrpc* currenly leaves the idlrpc server running in the background, which eventually has to be manually killed. This behavior will be fixed in the next version.


executing idl code
------------------

Here is a simple example for executing arbitrary IDL code from python:

.. code-block:: python

    # Import pyidlrpc.
    import pyidlrpc as idl

    # Execute a command in IDL.
    # This will print 'Hello mom!' in the idlrpc window.
    idl.execute("PRINT, 'Hello mom!'")


As a more complex example, we will now send some data back and forth between IDL and python.

.. code-block:: python

    import numpy as np
    import pyidlrpc as idl

    # Create a numpy array in python.
    py_array = np.random.normal(size=1000)

    # Copy this data to IDL.
    idl.setVariable('idl_array', py_array)

    # Calculate the standard devation and the mean in IDL.
    idl.execute('idl_stddev = STDDEV(idl_array)')
    idl.execute('idl_mean = MEAN(idl_array)')

    # Copy the results back to python.
    py_stddev = idl.getVariable('idl_stddev')
    py_mean = idl.getVariable('idl_mean')

    # Print out the results.
    print('Mean: {}, StdDev: {}'.format(py_mean, py_stddev))

 
calling functions and procedures
--------------------------------

In the examples above we used just the most basic pyidlrpc commands to control an IDL session and pass data between IDL and python. In these next examples we use the *pyidlrpc* wrapping routines to do simplify the code in the previous example significantly.


.. code-block:: python

    import numpy as np
    import pyidlrpc as idl

    # Create a numpy array in python.
    py_array = np.random.normal(size=1000)

    # Calculate the standard devication and mean using IDL.
    py_stddev = idl.callFunction('STDDEV', [py_array])
    py_mean = idl.callFunction('MEAN', [py_array])

    # Print out the results.
    print('Mean: {}, StdDev: {}'.format(py_mean, py_stddev))


wraping functions and procedures
--------------------------------

Wrapping functions or procedures looks very similar to the example above.  Let say we want to wrap the IDL ``STDDEV`` and ``MEAN`` functions in python module name ``idlmath``.


.. code-block:: python

    # idlmath.py

    import pyidlrpc as idl

    def stddev(*args, **kwargs):
        return idl.callFunction('STDDEV', args, kwargs)

    def mean(*args, **kwargs):
        return idl.callFunction('MEAN', args, kwargs)

That's all there is to it.  Now we can call these functions as though they were native python funcitons.

.. code-block:: python

    import numpy as np
    import idlmath

    array = np.random.normal(size=1000)

    # Here we transparently call the wrapped IDL functions.
    mean = idlmath.mean(array)
    stddev = idlmath.stddev(array)


wrapping objects
----------------

*pyidlrpc* also has the ability to fully wrap IDL objects.
           
Python wrapping objects should all inherit from PyIDLObject.  Here is an example of wrapping a hypothetical Gaussian object.

.. code-block:: python


    # _IdlGaussan.py

    from pyidlrpc import PyIDLObject

    class IdlGaussian(PyIDLObject):

        # Define the the IDL command needed to create the object.
        _creation_command = "OBJ_NEW"
        _creation_params = ['GAUSSIAN']
        _creation_keywords = None 

        def evaluate(self, *args, **kwargs):
            return, self.callFunction('EVALUATE', args, kwargs)

        def setParam(self, *args, **kwargs):
            return, self.callPro('SET_PARAM', args, kwargs)

        def getParam(self, *args, **kwargs):
            return, self.callFunction('GET_PARAM', args, kwargs)



