Category "Objects"


The recent article about how to investigate object code got me thinking about the various methods I use to find out about an object/class.

The code in the article, for the example of an object of class IDLgrColorBar, prints:

IDL> ab_obj_info, 'idlgrcolorbar'
Class: IDLGRCOLORBAR
Superclass: IDLGRMODEL
Superclass: IDLGRCONTAINER
Superclass: IDL_CONTAINER
Superclass: IDLGRCOMPONENT
Superclass: IDLITCOMPONENT</p>

as well as some HTML information listing the methods of the objects.

One of the most useful techniques for me is one of my library routines to find the class hierarchy of an object:

IDL> mg_class_hierarchy, idlgrcolorbar()
IDLGRCOLORBAR
IDLGRMODEL
IDLGRCONTAINER
IDL_CONTAINER
IDLGRCOMPONENT
IDLITCOMPONENT

This gives the same top-level information with a bit more detail (IDLgrContainer inherits from both IDL_Container and IDLitComponent), but does not provide any listing of the methods. If you know the name of the method, you can use another of my library routines to find out about it’s arguments:

IDL> man, 'idlgrcolorbar::computedimensions'
Filename: /Applications/exelis/idl85/lib/idlgrcolorbar__define.pro
result = idlgrcolorbar::computedimensions(self, osrcdest, PATH=path)

So I added a METHODS keyword to print out the methods of each class:

IDL> mg_class_hierarchy, 'idlgrcolorbar', /methods
IDLGRCOLORBAR
result = idlgrcolorbar::computedimensions()
result = idlgrcolorbar::init()
idlgrcolorbar::calcsize
idlgrcolorbar::cleanup
idlgrcolorbar::getproperty
idlgrcolorbar::setproperty
IDLGRMODEL
result = idlgrmodel::getctm()
result = idlgrmodel::getxyzrange()
result = idlgrmodel::init()
idlgrmodel::add
...

IDLdoc produces these docs, which list the methods of IDLgrColorBar and the hierarchy of superclasses along with a lot of other information including comments that might be in the code headers, but not the methods inherited from those superclasses.

An important side effect of the IDL-Python bridge is to add some Python features to IDL in order to be able to effectively expose a Python API in IDL. One of these is the introduction of “function pointers”, a way of treating an object as a function. This is useful because objects have a lot of nice properties just from being a variable – they can be stored in a variable, passed to other functions, saved in a .sav file, etc. With the introduction of function pointers, functions have these same properties.

To define an object to be used as a function pointer, a class needs to inherit from IDL_Object and define an _overloadFunction method:

function my_class::_overloadFunction, arg1, arg2, arg3, ...

Then, an object instantiated from this class can be called like a function and this method will be called to produce the result:

IDL> compile_opt strictarr
IDL> obj = my_class()
IDL> print, obj(1, 2, 3)

The “strictarr” or “idl2” compile_opt is needed or IDL will use the _overloadBracketsLeftSide method.

For example, let’s define a simple class that represents the histogram equalization image processing operation in mg_hist_equal__define.pro:

;= operators

function mg_hist_equal::_overloadFunction, im
  compile_opt strictarr

  return, hist_equal(im, percent=self.percent, top=self.top)
end

;= property access</p>
<p>pro mg_hist_equal::setProperty, percent=percent, top=top<br />compile_opt strictarr</p>
<p>if (n_elements(percent) gt 0L) then self.percent = percent<br />if (n_elements(top) gt 0L) then self.top = top<br />end</p>
<p>;= lifecycle methods</p>
<p>function mg_hist_equal::init, _extra=e<br />compile_opt strictarr</p>
<p>if (~self-&gt;IDL_Object::init()) then return, 0</p>
<p>self-&gt;setProperty, _extra=e</p>
<p>return, 1<br />end</p>

pro mg_hist_equal__define
  compile_opt strictarr

  !null = { mg_hist_equal, inherits IDL_Object, $<br />percent: 0.0, $
top: 0 }
end

The class defines a few properties that will be passed to the HIST_EQUAL function when called, but to be a function pointer, all that is needed is to inherit from IDL_Object and define the _overloadFunction method.

As an example of using this class, let’s read in an image:

IDL> dims = [248, 248]
IDL> file = filepath('convec.dat', subdir=['examples', 'data'])
IDL> mantle = read_binary(file, data_dims=dims)

Then define our function pointer:

IDL> he = mg_hist_equal(percent=10.0, top=255)

We could just call our function pointer like this:

IDL> equ_mantle = he(mantle)

But instead let’s define a function that applies any function pointer to a variable:

function mg_function_pointer_demo, im, op
  compile_opt strictarr

  return, op(im)
end

Then we can pass our function pointer to this function:

IDL> equ_mantle = mg_function_pointer_demo(mantle, he)
IDL> window, xsize=dims[0] * 2, ysize=dims[1]
IDL> tv, mantle, 0
IDL> tv, equ_mantle, 1

This code is in mg_function_pointer_demo.pro and can be called with:

IDL> .run mg_function_pointer_demo

Function pointers have many applications in such areas where an algorithm has a callback function such as fitting and optimization algorithms. The syntax of function pointers would be cleaner than passing function names as strings and also allows the various parameters of the function represented by the function pointer to be set before passing it.

See the IDL Data Point article for more information.

The biggest change introduced by IDL 8.4 is the treatment of all variables as objects. Every variable now has attributes that you would normally get returned from the SIZE function: length, ndim, dim, tname, typecode, and typename. For example:

IDL> a = bindgen(2, 3)
IDL> print, a.length
6
IDL> print, a.ndim
2
IDL> print, a.dim
2 3
IDL> print, a.tname
BYTE

There are also static methods available for all variables:

IDL> n = 50
IDL> print, n->toString()
50

Strings, numbers, integers, and pointers have their own set of special methods appropriate for their respective types. For example integers have some handle base conversion methods:

IDL> print, n->toHex()
32
IDL> print, n->toBinary()
110010

Strings have some methods that are not available through other IDL library routines, including the very useful replace method:

IDL> s = 'ITT VIS'
IDL> print, s.replace('ITT', 'Exelis')
Exelis VIS

Ronn Kling’s Object Oriented Programming with IDL is an excellent introduction to object-oriented programming with IDL. The beginning chapters introduce object-oriented concepts like encapsulation, inheritance, and polymorphism along with IDL’s basic syntax for creating classes. This should be straight-forward for anyone comfortable with writing normal routines in IDL.

Object Oriented Programming with IDL by Ronn Kling

Later chapters cover more sophisticated topics like features added in IDL 8.0 like object overloading, garbage collection, and the new syntax for calling methods and instantiating an object. There is also a chapter and appendix dedicated to showing tricks for getting around the lack of object-oriented features in IDL.

Overall, this is a great way to break the ice into object-oriented programming if you have been reluctant. Kling not only gives the basic syntax for IDL, but gives a nice introduction to object-oriented programming itself, as well as a discussion of situations when it can be most useful.

Operator overloading is the feature in IDL 8.0 that I have been testing the most; I am already using it in a couple of my projects. The behavior of objects when used in expressions containing nearly any of IDL’s operators can be defined through special methods of the object.

To make an object which recognizes operators, simply inherit from IDL_Object and write a special method for each operator you wish to use. For example, to make your object interact with the + operator, simply write a method with the signature

function myclass::_overloadPlus, left, right

When an object of your class is one of the operands of an expression using +, as in result = left + right, the special method will be called to compute result.

In addition to the standard unary and binary operators like +, le, unary -, etc., objects can also interact with other special routines and language keywords such as HELP, PRINT, SIZE, and FOREACH. Furthermore, the object can define behavior when it is accessed with []. It also automatically accesses properties when . is used (calling getProperty and setProperty as appropriate).

As an example of using operator overloading, I have some classes and routines for accessing HDF5 files (download). To see these classes in use, checkout the main-level program at the end of mg_h5.pro. Run it with:

ID> .run mg_h5

First, I have a convenience function mg_h5 which creates an object representing an HDF 5 file, an MGffHDF5File object:

IDL> h = mg_h5(file_which('hdf5_test.h5'))

The HELP routine will now present more information:

IDL> help, h
H MGFFHDF5F = </Applications/itt/idl/idl80/examples/data/hdf5_test.h5>

To make this work, I had to write the following special method:

function mgffhdf5file::_overloadHelp, varname
  compile_opt strictarr

  type = obj_class(self)
  specs = string(self.filename, format='(%"<%s>")')
  return, string(varname, type, specs, format='(%"%-15s %-9s = %s")')
end

An HDF 5 file is hierarchical, so groups in the file can be accessed using []:

IDL> g1 = h['images']

The method to implement this is a bit more complicated, but the signature is:

function mgffhdf5file::_overloadBracketsRightSide, isRange, $
                                                   ss1, ss2, ss3, ss4, $
                                                   ss5, ss6, ss7, ss8

Here, isRange is an array indicating whether each dimension specified using [] is a range (i.e., something like 0:10) or just a particular value (like 5 or 'images'). Then ss1 to ss8 are either 3 element arrays or scalar values, depending on the corresponding value in isRange.

One trick: it is possible to write classes that provide the ability to use operator overloading when used in IDL 8.0, but provide their normal functionality when used with IDL versions before 8.0. Just write your own IDL_Object class (here’s mine). This will not be found when using IDL 8.0, since its IDL_Object is a core built-in class, but will be found in older versions of IDL so that classes which inherit from it will compile.

I think that, used wisely, operator overloading can provide very usable, intuitive objects. But too much operator overloading can lead to overly complicated code which is difficult to debug.

ITT VIS is doing an object-oriented programming in IDL webinar on October 21.

It is frequently useful for applications to store information between runs. In general, if it is important enough to ask the user, it is important enough to remember. IDL provides a handy routine APP_USER_DIR to get a directory to store this type of information, but you are on your own reading and writing these preferences (unfortunately, PREF_SET and PREF_GET don’t allow application defined or user-defined preferences). MG_Prefs (code, docs) handles the dirty work for you by creating and restoring a SAV file for each of your preferences. This is a bit heavy-handed, but it means you store nearly anything as a preference value.

The class is intended to be simple to use. To store a preference do

prefs = obj_new('mg_prefs', author='mgalloy', application='myapp')
prefs->set, 'last_edited', 'myfile.txt'

Then in a later IDL session this preference can be retrieved with

prefs = obj_new('mg_prefs', author='mgalloy', application='myapp')
lastEdited = prefs->get('last_edited')

The template class is the basis for all the output in IDLdoc. In the process of releasing IDLdoc 3.0, some additions were necessary to facilitate outputting more complex hierarchical data.

Previously, the template process method took a structure argument and substituted variables in the template by the corresponding field name in the structure. The new version uses objects (but still allows structures). Any object with a getVariable method that follows the following signature:

function classname::getVariable, name, found=found

can be passed to the MGffTemplate::process method. When a name is encountered in the template, the template object calls getVariable with the variable name. This is much more flexible because variable requests can be handled in many ways: inherited from a parent class, delegated to another object, retrieved from a member variable, or retrieved in some other way (array, hash table, etc). Also, output can added to an object hierarchy by just adding a method instead of creating special structures.

The files needed to use the template are the MGffTemplate class (docs) and the MGffTokenizer class (docs).

See these previous posts about the template class.

True random numbers cannot be generated by a computer, but there are devices that can be connected to a computer that will generate true random numbers. Since not everyone has one of these devices, there are services that make these random numbers available through the internet.

Random numbers

Try random.org for random integers, sequences, and strings derived from atmostpheric noise. I have a simple class that uses the new IDLnetURL to access the website. It can be used like:

IDL> rnd = obj_new('MGrndRandom')
IDL> result = rnd->getIntegers(5, min=0, max=99, error=error)
IDL> help, error
ERROR LONG = 0
IDL> help, result
RESULT LONG = Array[5]
IDL> print, result
63 63 88 62 53

The class is available in my library (source, docs).

The QRBG (Quantum Random Bit Generator) Service generates the random numbers and transmits them to you. There are command line, C++, and Matlab interfaces, but no IDL interface (yet). The limits are 1 GB per session and a maximum of 10 concurrent connections, but has no hard limit to how many random numbers can be obtained in a given time period.

By the way, the (free) registration for the QRBG Service required solving the world’s best CAPTCHA:

Captcha test

I added methods to MGTestCase which are called before (setup) and after (tearDown) each test. Uses of these methods would be to create (and destroy) some common data for each of the tests in the MGTestCase, to time each test, or to do any task that must be done on a test-by-test basis as opposed to before and after all the tests in a MGTestCase (init and cleanup do that).

Now with fixtures

See “Unit testing framework” for an overview of the unit test framework.

Here’s an archive of all files needed and the docs for all the source code.

older posts »