
********
mirpyidl
********


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.  

*mirpyidl* requires a licenced installation of IDL .  Data is transfered back and forth using either the CallableIDL (idl_export.h) or the IDLRPC (idl_rpc.h) APIs, both of which are distrbuted with IDL. 

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

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

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


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


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

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

  * numpy
  * psutil

- cython

*mirpyidl* 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.


*mirpyidl* 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
============

*mirpyidl* can be installed from pypi.python.org using either easy_install or pip::

    easy_install mirpyidl
    pip install mirpyidl

*mirpyidl* can be alse be installed from source using the following command::
  
    python setup.py install

To do a user-local installaiton (which does not required root privalages) add the ``--user`` flag to the installation command.  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 *mirpyidl* module can be built in place using::

    python setup.py build_ext --inplace

This will build *mirpyidl* in place and create the ``mirpyidl.so`` shared library and ``mirpyidlrpc.py`` module both of which can then be imported from python.


Warning:
  On Mac OS X the setup script will change the installation names of the libidl.dylib, libidl_rpc.dylib and related IDL libraries to be prefixed with ``@rpath/``. This is the OS X standard way to specify run-path dependent libraries and is not known to have any side effects.

Note:
  Compilation with ``gcc`` should generally work without any issues.  Compilation with the intel compiler, ``icc`` is also possible, however the intel linker will also need to be used.  For compilation with ``icc`` make sure to set the following environmental variables: ``CC='icc -pthread'`` and ``LDSHARED='icc -pthread -shared'``.

Examples
========

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


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

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

.. code-block:: python

    # Import mirpyidl.
    import mirpyidl as idl

    # Execute a command in IDL.
    # This will print 'Hello mom!' from IDL.
    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 mirpyidl 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 mirpyidl commands to control an IDL session and pass data between IDL and python. In these next examples we use the *mirpyidl* wrapping routines to do simplify the code in the previous example significantly.


.. code-block:: python

    import numpy as np
    import mirpyidl 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 a python module named ``idlmath``.


.. code-block:: python

    # idlmath.py

    import mirpyidl 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
----------------

*mirpyidl* 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

    # _IdlGaussain.py

    from mirpyidl 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.callMethodFunction('EVALUATE', args, kwargs)

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

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


This wrapped object can now be used just like a normal Python object.

.. code-block:: python

   from _IdlGaussian import IdlGaussian

   obj = IdlGaussian()
   obj.setParam(location=0.0, width=1.0, area=1.0)

   y = obj.evaluate(0.0)


Using an idlrpc server
======================

*mirpyidl* can also connect to a running ``idlrpc`` instance rather than starting an IDL interpreter internally. To use an idlrpc connection simply use ``import mirpyidl`` instead of ``import mirpyidlrpc``. All of the examples above will work equivalently with this change.

.. code-block:: python

    # Import mirpyidlrpc to use the idlrpc interface.
    import mirpyidlrpc

    # We can also import individual classes as before.
    from mirpyidlrpc import PyIdl


Advanced usage can be found in the documentation.

When using the idlrpc interface an ``idlrpc`` server will need to be started in a separate process. This can be done using the following command (which is part of any standard IDL installation)::

    idlrpc
