IDL knows to kinds of so called heap variables which are global in scope, which means they are conserved in memory until explicitly deleted and can be referenced by any program part. They actually have many similarities but serve different purposes.
Pointers are used for slimline parameter passing and indirect acces of data. The are very useful with variable size structures. A pointer is created and linked to specific data with the ptr_new command.
a=findgen(5) b=ptr_new(a)
b is now a pointer linked to the array a. To reference to its contents, it is preceded by an asterisk ('*').
IDL> print,*b 0.00000 1.00000 2.00000 3.00000 4.00000
A pointer now can be passed to a subroutine to make a variable known there.
pro pointer1,pointer print,*pointer end
Calling this routine now equally prints the array.
IDL> pointer1,b 0.00000 1.00000 2.00000 3.00000 4.00000
Instead of passing the array a to the subroutine, we passed just one scalar value, thus saving memory.
Unlike C, pointer arithmetic is illegal.
A null pointer is provided for logical testing. Note the diference between a null pointer (which later cannot be given a valid address) and an undefined pointer.
IDL> a=ptr_new() IDL> b=ptr_new(/allocate_heap) IDL> c=findgen(10) IDL> *b=c IDL> *a=c % Unable to dereference NULL pointer: A. % Execution halted at: $MAIN$
Arrays of pointers are created with the ptrarr command.
Pointers may point to pointers.
In order to maintain large programs they have to be broken in modules. In conventional programming these modules are subroutines or functions that perform well defined operations over a set of input data and return values to the calling program. This form of programming is oftenly called procedural programming.
Though procedural programming seems to be a 'natural' way of breaking programs into pieces it has shown to have some serious drawbacks. Error tracking often is problematic, errors tend to have consequences very far away from the point they occur, reuse of procedural programs is low.
Over the years a different form of programming has become increasingly
popular, object oriented
programming. In this case, theindividual modules are confined sets
of data and procedures (called methos)
defined on them. Although for the experienced procedural programmer
this seems clumsy and
inconvenient, it has been shown that the encapsulation yields higher
reliability and better reuse.
To define an object in IDL first its characteristics have to be defined. This is called a class definition and done in a corresponding file. If the object class has the name fimage then it is defined in a file calledf image__define.pro (TWO underscores). Let us define a class of objects that are composed of a header and a twodimensional real data array 512x512 in size. The definition is done by a structure:
; ; define 512x512 fits image ; pro fimage__define result={fimage, header:strarr(36), data:fltarr(512,512)} end
Now we have an object class 'fimage' from which we can stamp out individual objects, e.g.
IDL> image1=obj_new('fimage') IDL> image2=obj_new('fimage') IDL> help,image1,image2 IMAGE1 OBJREF = <ObjHeapVar1(FIMAGE)> IMAGE2 OBJREF = <ObjHeapVar2(FIMAGE)>
However, all we can't do anything with these objects (except deleting them).
We need some methods
that work upon them. These should be declared in the same definition
file, preferrably before the
object itself in order to be compiled automatically when the object
class is created. The declaration
is done similar to ordinary procedures but by preceeding the method
name with the object class and
two colon. Within the defining program access to the data structure
is providid via a
structure called self.
We will define two methods on our objects 'fimage', one that reads
in data from a
fits file, the other that visualizes the data.
; ; module to read 512x512 image ; function fimage::read, filename result=mrdfits(filename,0,res_header) result_size=size(result) ; ; check for correct size ; if ( (result_size(1) ne 512) or (result_size(2) ne 512) or $ (result_size(0) ne 2)) then return,-1 help,result help,res_header self.data=float(result) self.header=res_header(0:35 < n_elements(res_header)-1) return,0 end ; ; display feature ; pro fimage::display tvscl,self.data end ; ; define 512x512 fits image ; pro fimage__define result={fimage, header:strarr(36), data:fltarr(512,512)} end
Methods are called by the -> operator. To read in and visualize an image we can now proceed
IDL> result=image1->read('./man.fits') IDL> image1->display
Exercise: Create an object class of images independent of size.
Objects are deleted by the obj_destroy,<object_name> command.
There are two implicit methods, init and cleanup that, if present, are invoked on creation or deletion of an object.
function incl::init self.x=5. print,' variable initialized to 5 return,1 end pro incl::cleanup print,' object deleted' return end pro incl__define result={incl, x:0.} end
Note that the init method is a function and has to return a value 'true'. If it returns 'false', a null object is created instead.
IDL> a=obj_new('incl') variable initialized to 5 IDL> obj_destroy,a object deleted
Object classes already defined can inherit definitons to other classes. Let us assume that we want to create objects basically the same as 'fimage' but they will be elements of a series
pro serial_fimage__define result={serial_fimage, serial_number:0, INHERITS fimage} end
Objects of this class have all the properties defined for the 'fimage'
class, referring to data structures
as well as methods.
Most of non widget graphics in IDL are available as objects. Except from an object oriented approach to plotting, the advantage of graphical objects is that they are device independent. Once completely defined they can be send to output devices such as screens or printers and should deliver identical results.
However, the price tag is high. IDL considers a graphical object tree to be a projection of a three dimensional data space renders all objects within this space. This means that even most simple graphics will lead to enormous files.
A graphical object tree consists of a four level hierarchical structure:
A view is the smallest entity that can be realized on a screen or printer. Graphics atoms are analogous to the basic plotting routines in direct graphics, like plot, polygons, surface, etc. As an example let us draw a sine and cosine wave. First we create the basic plot objects. The corresponding object classes are called IDLgr<name> where 'name' is the same as the name of the direct graphics procedure.
IDL> data=findgen(101)/100.*2.*!pi IDL> sine=obj_new('IDLgrPlot',sin(data),linestyle=0) IDL> cosine=obj_new('IDLgrPlot',cos(data),linestyle=2)
Now these objects have to be put into a model, a class 'IDLgrModel' provides methods like add or remove to do the job.
IDL> mymodel=obj_new('IDLgrModel') IDL> mymodel->add,sine IDL> mymodel->add,cosine
Models can be rotated in 3D space and then have to be put into views. In our example there is not much sense in transforming them, we put them immediately into the view. Some care has to be spend at this step. As models can be added or deleted at any time from the view, there is no reasonable default for the size of the viewplane. We will define it here so the plot fits in with some reasonalbe margin. The default units are data units.
IDL> myview=obj_new('IDLgrView',viewplane_rect=[-10,-1.2,120,2.4]) IDL> myview->add, mymodel
Now we are ready to visualize this structure. The IDLgrWindow class has a method draw that will do the job.
IDL> mywin=obj_new('IDLgrWindow') IDL> mywin->draw,myview
All objects must be explicitly deleted with obj_destroy, just closing the window with the window controls maintains the window object in memory. Observe that contrary to the 'plot' procedure no axis have been drawn. Axis must be added as object from the IDLgrAxis class.
To print the same graphics, the draw method has to applied on a IDLgrPrinter
class object. This
may be configured to print to a postscript file instead of printing
directly on a printer.