User Tools

Site Tools


internal:administration:idl

This is an old revision of the document!


Table of Contents

Jans IDL tips

Dies ist eine Sammlung meiner idl Entdeckungen This is a collection of my IDL discoveries

In the web you may find many IDL tutorials or helb pages …

IDL current reference @ NV5 Geospatial software :
IDL 8.9. Reference @ NV5
IDL 8.9. funclisting @ NV5
IDL 8.9. whats new @ NV5

IDL 8.9. references @ l3harris geospatial

IDL 8.8 referecnes (dead, Certifacte expired ??? ) It was once Exelis, but they deviate to harris:
refefrences … links to newest version at harris

A site which provides the IDL-help pages which you also cen get at the IDL prompt with IDL>? functionname
https://www.irya.unam.mx/computo/sites/manuales/IDL/idl_Left.html

At FZ-Jülich you can find a manual for IDL programming:
Praxisbezogene IDL Programmierung

You can get Help from IDL by just typing ? at the IDL prompt or for a certain idl function by typing ? <functioname>. This will open your web browser with a local help page.

We once decided to use a revision control system (RCS) for the idl routines under /home/hatpro/idl

Configuration

IDL uses some Environment variables containing the pathes to its libraries etc. see here . Internally some of them can be accessed as system variables: The environment varibles usually have the form IDL_<NAME>. Inside IDL they are accesible via !<NAME> Basic path to IDL is in environamet variable IDLDIR inside IDL it is accesible via !DIR which is currently (idl version 9.0) /opt/nv5/idl90 .

GDL

There is an alternative for IDL based on the same syntax, function names etc.:
Gnu Data Language (GDL)

http://gnudatalanguage.sourceforge.net/
http://linux.die.net/man/1/gdl
weiterführende hilfe für die installation
list of functions missing in GDL
… auf der qeb ist das standard ubuntu paket gnudatalanguage version 0.9 installiert.
Call with
$>gdl
But it is since years in development and many functions are missing or not working properly.
See this list: /home/jschween/ideen/gdl/readme.txt

Starten eines Scirptes von der Shell geht in GDL 0.9 nicht wie in IDL mit \$> idl @idl_script sondern über eine bash pipe:
\$> cat @idl_script | gdl

Einige Besonderheiten: siehe diese einfachste Einleitung in GDL

  • shell Befehle müssen nicht per spawn gestartet werden sondern können in einer Zeile die mit \$ beginnt stehen, z.B.:
    GDL> $ cp idl_script.pro gdl_script.pro

Basics

start from the terminal prompt:

~>idl

Help with

IDL> ?

or

IDL> ? command-name

idls own routines are located in

/usr/local/itt/idl64/lib

simple examples

hello world

print, 'hello world !'

plot

  ; number of data points
  N = 100
  ; an array of x-values in interval 0..1
  x = findgen(N)/N
  ; an array of y-values
  y = sin(x*2*!Pi)
  ; plot the data
  plot, x, y

add axis titles and title to plot:

  plot, x,y , title='this is the title' , xtitle='x-axis', ytitle='ordinate'

manipulate the range

  plot, x,y , xrange = [ -0.1 , 1.5 ] , xstyle=1 , yrange = [ -1.2 , +1.5 ] , ystyle=1

use linestyle and colors

  plot, x,y , linestyle=1, color=3

plot a second function over the first

  oplot, x, 0.6*sin(2*x) , linestyle=2, color=255

export data to file

  openw, file, 'data.txt',  /Get_LUN
  printf, file , 'x=', x 
  printf, file , 'y=', y
  close, file
  free_lun, file

calculus

  ; add two to all values
  x2 = x+2
  ; multiply all by three
  x3 = x*3
  ; square and squareroot
  x_square = x^2
  x_root = sqrt(x)
  ; mean etc.
  ym = mean(y)
  yvar = variance(y)
  ysigma = stddev(y)
 

general syntax:

<command> [ , <arguments> ] [ , <name>=<variable> ] [ , /<flagname> ]
  • be aware of the comma !
  • lowercase/uppercase does not matter
  • <arguments> is a comma separated list like x,y,z which must come in the specified order
  • the last arguments can be omitted, e.g. x,y instead of x,y,z (if <command> can handle this )
  • <name>=<variable> are named variables inside command, these parameters can usually be omitted
  • <name> arguments must not come in a special order
  • /<flagname> is an abbreviation for flagname=1

change working dir

cd , "<path>" [ ,current=<var> ]

path in single (') or double (“) quotation marks !

execute shell command : SPAWN

SPAWN [, "<Command>" [, Result] [, ErrResult] ]

command in single (') or double (”) quotation marks !

e.g. pwd = print working directory:

IDL>SPAWN , 'pwd'

In some cases you may get error message from your program. For example curl:

IDL> spawn, 'curl -V'
curl: /opt/nv5/idl90/bin/bin.linux.x86_64/libcurl.so.4: no version information available (required by curl)
...
WARNING: curl and libcurl versions do not match. Functionality may be affected.

The first and the last lines tell you that libcurl.so.4 from the /opt/nv5/idl90/... path makes problems. But why should curl try to load libraries from /opt/nv5/… i.e. the IDL folder ?

Another example is ghostscript (gs) - which does not even work:

IDL> SPAWN, 'gs --version'
gs: /opt/nv5/idl90/bin/bin.linux.x86_64/libstdc++.so.6: version `GLIBCXX_3.4.32' not found (required by /lib/x86_64-linux-gnu/libgs.so.10)

Again ghostscript tries to load lubraries from /opt/nv5/…

To get around you have to understand where the system seeks libraries. This is explained e.g. here: https://askubuntu.com/questions/1500913/find-default-library-paths-location-in-ubuntu, or in the man pages of ld.so. To ensure that IDL only uses libraries of a certain version they set the variable LD_LIBRARY_PATH to the idl library folders. It is not set in standard bash environment, but in the SPAWN environment as it is a subrpocess of IDL:

IDL> SPAWN, 'echo $LD_LIBRARY_PATH'
/opt/nv5/idl90/bin/bin.linux.x86_64:/opt/nv5/idl90/bin/bin.linux.x86_64/dm/lib:/opt/nv5/idl90/bin/bin.linux.x86_64/jre/lib/server:/opt/nv5/idl90/bin/bin.linux.x86_64/jre/lib/server/..:/opt/nv5/idl90/bin/bin.linux.x86_64/jre/lib/server/../native_threads

We can ask Linux where we can find the libraries needed:

$whereis libcurl.so.4
libcurl.so.4: /usr/lib/x86_64-linux-gnu/libcurl.so.4

and

$whereis libstdc++.so.6
libstdc++.so.6: /usr/lib/x86_64-linux-gnu/libstdc++.so.6

We have several possibilities:

SETENV, 'LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/:'+GETENV('LD_LIBRARY_PATH')

  • Alternatively adapt LD_LIBRARY_PATH within the SPAWN environment when calling your program

SPAWN, 'LD_LIBRARY_PATH=/usr/lib:$LD_LIBRARY_PATH; your_command_here'

  • or even simpler unset this path variable in SPAWN: it is not needed as we are not doing IDL specific things, and when SPAWN is finished this is forgotten again.

SPAWN, 'unset LD_LIBRARY_PATH; your_command_here'

According to IDL help about Environment variables LD_LIBRARY_PATH is used for the Java libraries - i.e. probably for the IDE and its graphical interface. That means if you do not use the IDE the first solution will have no problems.

Check with curl:

  SPAWN, 'unset LD_LIBRARY_PATH ; curl -V'
  curl 8.5.0 (x86_64-pc-linux-gnu) libcurl/8.5.0 OpenSSL/3.0.13 zlib/1.3 brotli/1.1.0 zstd/1.5.5 libidn2/2.3.7 libpsl/0.21.2 (+libidn2/2.3.7) libssh/0.10.6/openssl/zlib nghttp2/1.59.0 librtmp/2.3 OpenLDAP/2.6.7
  ...

Check with ghostscript

  SPAWN, 'unset LD_LIBRARY_PATH ; gs --version'
  10.02.1 

⇒ WORKS!

compile and run idl programs

to just compile do

.COMPILE <filename_1> [,<filename_2> [...]]

to compile and run:

.RUN <filename_1> 

with <filename_1> without the extension “.pro”

general structure

The semicolon ';' is the comment sign: everything after it is ignored by the interpreter

a PROGRAM is a text file with IDL commands ending with an END statement (lower and uppercase is allowed, dont mix cases !)

print, 'Hello world !'
print, '3*4=', 3*4
x = 3
y = 2.5
z = x/y
print, 'x=', x, ' y=',y,' x/y=z=', z
print, 'its time to say goodbye'
END

decisions are made with IF THEN ELSE

IF x eq 4 THEN print, 'x equals 4' ELSE print, 'x is not 4'

this must be in one line but can be extended of several lines with the '$' character:

IF x eq 4 $
  THEN print, 'x equals 4' $
  ELSE print, 'x is not 4'

or with BEGIN END blocks

IF x eq 4 THEN BEGIN
  print, 'x equals 4' $
END ELSE BEGIN
  print, 'x is not 4'
END

for safety you may extend the END statements with blocked statement i.e. ENDIF or ENDELSE

you may nest i.e. build structures like IF … THEN … ELSE IF … THEN … or use the CASE statement.

Loops can be implented with FOR, WHILE or REPEAT as one-liners:

FOR i = 0,5 DO PRINT, i
i = 0  
WHILE i lt 6 DO i = i+1

i = 1
REPEAT i = i + 1 UNTIL i gt 5

for more complex loops you may use BEGIN … END

FOR i = 0,5 DO BEGIN
  PRINT, i
  END

i = 0
WHILE i lt 6 DO BEGIN
  i = i+1
  PRINT, i
  END

i = 1
REPEAT BEGIN
  i = i + 1 
  PRINT, i
END UNTIL i gt 5

to make coding a bit safer you may append the name of the loop command to the END statement, i.e.

ENDFOR 
ENDWHILE
ENDREP 

a PROCEDURE is a subroutine that may take parameters and change them. Its definition is started with the word PRO and ends with a END statement

PRO my_proc, text_str, x_value=x_value
  print, 'text_str=', text_str
  print, 'x=', x_value
  x_value = x_value + 2
  END ; pro

it is called with its parameter list separated by commas:

x = 2
my_proc, 'some text', x_value=x

In this case x will have the value x+2 after the call

if a procedure is stored in a file with the same name as the procedure IDL will compile this file …

A FUNCTION is a subroutine that may return a result

FUNCTION my_func, text_str, x_value=x_value
  print, 'text_str=', text_str
  print, 'x=', x_value
  x_new = x_value + 2
  RETURN, x_new
  print, 'this is never printed !'
  END ; function

return means that the result is returned but also that the execution returns from the sub subroutine, for that reason the last print command is ignored.

A function is called with its parameterlist in braces

x = 2
y = my_func( text_str, x_value=x )

idl path

When finding a new name which might be a function procedure or another idl program idl searches in the directories given by the idl sytem variable !PATH. To make own programs visible to idl the directory where these programs could be found must be included in the path:

!PATH = !PATH + ':/home/hatpro/idl/lib'

To ensure that the directory is not added again an again use the following code:

s = '/home/hatpro/idl/lib'
if strpos( !PATH , s ) lt 0 then !PATH = !PATH + ':' + s

defining constants

Constants are defined by value, the type can be specified

<name> = <Value>[<Type Letter[s]>]

e.g.:

  • i = 0b ; creates a 1byte=8bit integer constant of value zero (0..255)
  • i = not 0b ; creates a 8bit integer with all bits set to 1 i.e. value 255decimal='ff'x='11111111'b
  • i = 0S ; creates a signed 16bit integer constant of value zero (-32767..32767)
  • i = 0US ; creates an unsigned 16bit integer constant of value zero (0..65535)
  • z = 0L ; creates a signed 32bit integer constant (Long) of value zero (+/-2.2mrd)
  • z = 0UL ; creates an unsigned 32bit integer constant (Long) of value zero (0…4.mrd)
  • z = 0LL ; creates a signed 64bit integer constant (Long) of value zero (+/-9.2e18)
  • z = 0ULL ; creates an unsigned 64bit integer constant (Long) of value zero (0…1.8e19)
  • x = '0F'XU ; an unsigned integer (U) written as Hex (X) with (decimal) value 15
  • x = '08'OU ; an unsigned integer (U) written as Octal (O) with (decimal) value 8
  • y = 7.3E1 ; creates single precision 73.0
  • z = 7.3D1 ; creates double precision 73.0
  • novalue = !VALUES.F_NAN ; creates a single precision floating point NAN ('not a number')
  • novalue = !VALUES.D_NAN ; creates a double precision floating point NAN ('not a number')
  • infty = !VALUES.F_INFINITY ; creates a single precission infinity (positive, a minus in front makes it negative)
  • infty = !VALUES.D_INFINITY ; creates a double precission infinity

see 'defining constants' and 'data types in IDL help.

For NAN and infinity see 'Special Floating-Point Values' or Constant System Variables

single precission is a 4 Bit floting point number (~range: -1e38..1E38 accuracy ~1e-7),
double precision is a 8Bit floating point number (~range: -1E308..1E+308 )
see wikipedia floating point.

arrays

can be generated with

x1d = fltarr( N ) ; type float
x2d = dblarr( Ni, Nj ) ; type double precision
...

The arrays will be filled with zeros, see e.g. help for fltarr or the info page about array creation

To fill the array with a certain value:

x2d[*] = -1.0

To fill it with the value of its running index:

x1d = findgen( N ) ; float type
x2d = sindgen( Ni, Nj ) ; string type
...

For details see here

You may also use makearray:

s1d = MAKE_ARRAY( Ni, /string, /index )
x2d = MAKE_ARRAY( Ni, Nj , /float, value=-1.0 )
...

Multidimensional arrays can be accessed via indices for all the different dimensions but also via less indices:

x3d = fltarr( Ni, Nj, Nk )
print, x3d[i1d] ; returns the i1d scalar element of the array
print, x3d[i,j] ; returns the i+j*Ni scalar element of the array
print, x3d[i,j,k] ; returns the i+j*Ni+k*Ni*Nj((correct ?)) scalar element of the array
...

For two dimensional arrays the relation between one and two dimensional indices is:

i1d = i + j*Ni
i = i1d mod Ni
j = i1d / Ni

This can also be done with the idl function ARRAY_INDICES

structures

A structure is a dataype which joins variables of different type in one variable. It can be generated with (see http://www.harrisgeospatial.com/docs/Creating_and_Defining_St.html)

var = { name : 'bill' , int1:1 , a2:2.4 }

or with the CREATE_STRUCT function (see http://www.harrisgeospatial.com/docs/create_struct.html)

var = CREATE_STRUCT( 'name', 'bill' , 'int1' , 1 , 'a2' , 2.4 }

usage is

var = CREATE_STRUCT(  [Name=<string>, ] <name1>, value1, [<name2>, value2, ... ] ...    )

where Name registers this strucure to IDL and forces you to not deviate from the list of names and types … or

var = CREATE_STRUCT(  [Name=<string>, ] <namelist>, value1, [ value2, ... ] ...    )

with <namelist> a string array e.g. <namelist> = [ 'name', 'int1', 'a2' ]

CREATE_STRUCT allows to append structures

var = CREATE_STRUCT(  [Name=<string>, ] [struct_1, ...] <name1>, value1, [<name2>, value2, ... ]  [struct_2,...]    )

Where struct_1, and struct_2 are previosly defined structures.

reference is made with

print, var

or with the . operator:

print, var.name
print, var.int1
...

if you do not know the elements of a strucuture you may use help with the flag /strucutre

help, var, /structure

or the TAG_NAMES function

tn = TAG_NAMES(var)
print, tn

You can construct arrays of structures with the REPLICATE function and manipulate its content and find certain elements (see http://www.harrisgeospatial.com/docs/Arrays_of_Structures.html):

; create array of 20 structures var as e.g. defined above:
var_list = REPLICATE( var , 20 )
; change name of entry name of structure number three
var_list[3].name='ff'
; find this entry
print, where(var_list.name eq 'ff' )
   

LIST and HASH

With version 8.0 IDL introduced LIST and HASH.

  • LIST is an array like datatype which may contain a mixture of different variable types and structures. Elements can be accessed via their index (see LIST help page at irya.unam.mx)
  • HASH is a structure like dataype which elements can be accessed via strings containing the names and which is more flexible than the STRUCUTRE data type (see HASH help page at irya.unam.mx)

In the following value can be of any IDL datatype.

Create a LIST:

x_list = LIST( Value1, Value2, ... )

Add a single element

x_list.ADD, value
...

Create a HASH:

y_hash = HASH( [Key1, Value1, Key2, Value2, ... )

Add another HASH

y_hash = y_hash + HASH( [Key21, Value21, ... )
...

Both datatypes are objects with several methods to get information or manipulate its contents - se help page referenced above.

The FOREACH statement can be applied to both data types. Assuming that you can print all their elements you can do this:

FOREACH x in x_list do print, x
FOREACH y in y_hash do print, y
...

matrix multiplication

IDL comes with two matrix multiplication operators # and ##.

  • lets assume we have A=fltarr(NAcol,NArow) and B=fltarr(NBcol,NBrow)
  • operator # multiplicates rows of the left by columns of the right matrix
    ⇒ it must be NArow=NBcol and we get C = A # B = fltarr(NAcol,NBrow)
    it is C[i,j] = sum(k=0..NArow-1) A[i,k] * B[k,j]
    scheme:
                          bbb
                aaaaa     bbb     ccc
C  =  A # B  =  aaaaa  #  bbb  =  ccc
                aaaaa     bbb     ccc
                          bbb
  • operator ## multiplicates columns of the left by rows of the right matrix
    ⇒ it must be NAcol=NBrow and we get C = A ## B = fltarr(NBcol,NArow)
    it is C[i,j] = sum(k=0..NAcol-1) A[k,i]*B[j,k]
    scheme:
                 aaa               cccc
                 aaa      bbbb     cccc
C  =  A ## B  =  aaa  ##  bbbb  =  cccc
                 aaa      bbbb     cccc
                 aaa               cccc 
  • you may use this to calculate the scalar product of two vectors:
    s = transpose(x) # y or s = x ## transpose(y) with x = fltarr(N) and y = fltarr(N)
  • IDL is somewhat sloppy with the relation between vectors and matrices, i.e. one and two dimensional arrays:
    • lets assume X = fltarr(N) and Y = fltarr(N)
    • the following gives the same N*N matrix:
      • C = X # Y
      • C = X # transpose(Y)
    • and these give the transpose of C (D = transpose(C))
      • D = X ## Y
      • D = X ## transpose(Y)
  • you may generate Matrices from vectors, i.e. from one-dimensional arrays:
  • with X=fltarr(Nx) and Y = fltarr(Ny) you get (again using the flopiness of IDL)
    • C = X # Y or C = X # transpose(Y) ⇒ C = fltarr(Nx,Ny)
    • C = X ## Y or C = transpose(X) ## Y ⇒ C = fltarr(Ny,Nx)
  • in linear algebra a product between two vectors giving a matrix is called the 'outer product' or in german 'Dyade'
  • you may use this to generate matrices with equal rows or columns:
    • to generate a Matrix with Ny equal rows x = fltarr(Nx) use
      XX = x # replicate(1,Ny)
      i.e. replicate(1,Ny) is interpreted as a 1*Ny matrix
    • to generate a Matrix with Nx equal columns y = fltarr(Ny) use
      YY = replicate(1,Nx) # y
      i.e. replicate(1,Nx) is interpreted as a Nx*1 matrix - odd, he ! - try to be not confused !
                         1     xxx
                         1     xxx
XX  =  X # E  =  xxx  #  1  =  xxx
                         1     xxx
                         1     xxx
                         y     yyy
                         y     yyy
YY  =  E # Y  =  111  #  y  =  yyy
                         y     yyy
                         y     yyy 
  • One can use this to generate matrices with a variable increasing only in one dimension and arrays of function values:
; generate Nx*Ny Matrix xx with Nx values increasing in x-direction from xa to xb 
x = xa + (xb-xa) * findgen(Nx)/(Nx-1) 
xx = x # replicate(1.0,Ny)

; generate Nx*Ny Matrix yy with values increasing in y-direction from ya to yb
y = ya + (yb-ya) * findgen(Ny)/(Ny-1) 
yy = replicate(1.0,Nx) # y

; Nx*Ny matrix with distances from a point with coordinates (cx,cy)
r = sqrt( (xx-cx)^2 + (yy-cy)^2 )

; Nx*Ny matrix with function values on every point 
f_xy = f( xx, yy )
  • As matrix operations are faster than for-loops this may speed up your code a lot …

procedure names

if an unknown procedure name <fname> is found idl looks in

  1. the current directory
  2. in the directories specified in IDLs '!PATH'-variable

for a file <fname>.pro or <fname>.sav

letters of the filename should be all lowercase.

the !PATH variable ist constructed as

<dir1>:<dir2>:...

function names

if idl encounters an expression like

y = f_phi2( a )

It assumes firstly an array variable f_phi2 of which the index a is desired.
If f_phi2 is an external function it should be anounced with the command

FORWARD_FUNCTION f_phi2

see also http://www.physics.nyu.edu/grierlab/idl_html_help/F33.html#wp872909

Blocks

Blocks are generated with:

BEGIN
...
END

where END can expanded to

ENDIF   	

if it terminates an IF THEN … block

ENDWHILE

if it terminates a WHILE DO … block …

This naming of the END key word is helpful to find errors in the BEGIN…END structure

Interaktion

soll laut idl mit widgets gemacht werden - ist aber aufwändig !!

Tastatur

es gibt zwei einfache methoden mit der Tastatur zu Arbeiten:

  • der Read-Befehl erwartet eine Eingabe die mit RETURN abgeschlossen werden muss
  • der GET_KBRD erlaubt eine direkte Tastaturabfrage:

der Read-Befehl:

c = ' '
read,'continue with RETURN',c

mit GET_KBRD kann man Tastaturabfragen machen:

  Result = GET_KBRD([Wait] [, /ESCAPE | , /KEY_NAME])

Zum Beispiel:

key = GET_KBRD( 0 )

liefert in key die letzte gedrückte Taste oder '' (nullstring) wenn keine Taste gedrücktwurde …

key = GET_KBRD( 1 )

wartet auf tastendruck und liefert in key die letzte gedrückte Taste

key = GET_KBRD( 1 , /escape )

wartet auf tastendruck und liefert in key die letzte gedrückte Taste oder die escape sequenz die der Taste entspricht - also zb. string(byte([27,91,66])) für die 'pfeil nach unten' taste

Liste verschiedener escape sequenzen:

Taste escape sequence
cursor n. oben 27 91 65
cursor n. unten 27 91 66
cursor n. rechts 27 91 67
cursor n. links 27 91 68
ende 27 91 70
pos 1 27 91 72
Einfg 27 91 50 126
Entf 27 91 51 126
seite n. oben 27 91 53 126
seite n. unten 27 91 54 126
ESC 27 27

damit lässt sich ein einfache block aufbauen mit dem man ein Programm steuern kann:

  • das Programm soll eine variable i hoch zählen
  • anhalten wenn ' ' (space) gedrückt wird
  • eins weiter (i+1) gehen wenn '>' oder 'cursor nach rechts' gedrückt wird
  • eins zurück (i-1) gehen wenn '<' oder 'cursor nach links' gedrückt wird
  • an den anfang (i=0) gehen wenn '0' oder 'pos 1' gedrückt wird
  • an das Ende (i=N-1) gehen wenn 'E' oder 'Ende' gedrückt wird
  • beenden wenn ESC gedrückt wird
i = 0
finished = 0
key = ' '
while not finished do begin
 
  ; ... do something ...
  print, i,' ... '
 
  ; check keyboard - return escape sequences 
  ; if the last key was a space - wait
  if key ne ' ' then key = GET_KBRD( 0 , /ESCAPE ) $
                else key = GET_KBRD( 1 , /ESCAPE )
 
  ; if we got a key
  if key ne '' then begin
    if key eq ' ' then begin
      ; ' '(space) => increment counter
      if i lt N-1 then i += 1
    endif else if (key eq '<') or (key eq string(byte([27, 91, 68]))) then begin
      ; '<' or cursor left => go back
      if i gt 0 then i -= 1
      key = ' '
    endif else if (key eq '>') or (key eq string(byte([27, 91, 67]))) then begin
      ; '>' or cursor right => go further
      if i lt N-1 then i += 1
      key = ' ' 
    endif else if (key eq '0') or (key eq string(byte([27, 91, 72]))) then begin
      ; '0' or pos 1 => go to start
      i = 0
      key = ' ' 
    endif else if (key eq 'e') or (key eq string(byte([27, 91, 70]))) then begin
      ; 'e' or Ende => go to end
      i = N-1
      key = ' ' 
    endif else if key eq string(byte([27,27])) then begin
      ; ESC-key => set stop flag
      finished = 1
    endif
  endif else begin ; no key pressed
    ; increment counter
    if i lt N-1 then i += 1
    ; set 'stop flag' if at end of data
    if i eq N-1 then key = ' '
    print,'End of data ...'
  endelse
 
endwhile 

Dieser code findet sich als funktion key_control.pro in hatpro/idl/lib.

mouse

mit der funktion cursor es ist möglich die Mausposition abzufragen:

  ; wait for mousebutton down event
  cursor, mx,my, /down

oder

  ; get mouse position imediately - point coordinates in data-coordinates
  cursor, mx,my, 0, /data

Information über die gedrückte Maustaste erhält man über die systemvariable !Mouse.button (linke maustaste == 1, rechte ==2, mitte == 4).

Eine Anwendung findet sich in mouse_clip.pro in hatpro/idl/lib/.

strings

can be split by

s2 = STRMID( s , index , [length , ... ] )

position of substrings with

i = STRPOS( s , sub [ ... ] )

split string at character sep into substrings, return array of substrings (sx) and give number of substrings (N)

sx = STRSPLIT( s , sep , count=N, /extract )

can be used to separate columns - see text files below…

you can also use regular expressions - e.g. lets assume you have strings with variable names and unit in braces:

s = 'wind speed (m/s)'

then you may want to split at the opening and closing braces:

ss = strsplit( s, '(\(|\))', /extract, /regex )

will deliver

ss[0] = 'wind speed'
ss[1] = 'm/s'

join array of strings into one string with separating character sep

s = STRJOIN( s , sep )

filenames can be separated into path and name by

file_dirname( <path to file> )
file_basename( <path to file> )

formatted output

Formatted output is generated with a syntax similar to FORTRAN:

PRINT, 'ABC..', 1, 2, 1.2, FORMAT='(A8,I," / ",I03," * ",F5.1)'

generates:

 ABC..      1  / 002 *   1.2

see widhts and positions:

 123456781234  / 123 * 12345

where the FORMAT='( ... )' specifier said:

  • A8 : print as alpha-numerc-string of 8 characters width
  • I : print as integer with standard width (8 digits, leading blanks)
  • “ / ”: insert string ' / '
  • I03: print as integer with 3 digits width and leading zeroes
  • “ * ”: insert string ' * '
  • F5.1: print as flaoting point with total width 5 and one digit after the decimal point

To print an array x=fltarr(N) with N ≥ 10 into a table with colums separated by tabs (ascii code 9) use:

TAB=STRING(9b)
PRINT, 'x=',x,FORMAT='(A,10("'+TAB+'",F8.2))'

Time as Julian date can be formatted as well. To get a date as DD.MM.YYYY hh:mm:ss use:

PRINT, t, FORMAT='(C(CDI02,".",CMoI02,".",CYI04," ",CHI02,":",CMI02,":",CSI02))'

Where format included:

  • C(…) : print in calendar format
  • CDI02 : calendar format for day as 2 digit integer with leading zero
  • CMoI02 : calendar format for Month as 2 digit integer with leading zero
  • CYI04 : calendar format for Year as 4 digit integer with leading zeroes
  • CHI02, CMI02, CSI02 : calendar format for hour, minute, second as 2 digit integers with leading zeroes

It is possible to print seconds as float:

PRINT, t, FORMAT='(C(CHI02,":",CMI02,":",CSf05.2))'

General syntax of a Format specifier is

[<repeat>]<Format code>[+|-]<width>[.<precision>[E<exponent>]]

Where <repeat> <width> and <precision> are integer numbers.

  • <repeat> says that the format code is repeatedly used.
  • <width> is the width of the whole expression, <width>=0 means that the width is adapted to the number, with <width>=03 leading zeros are set to fill a three digit field.
  • <precision> for floating point numbers gives the digits after the point, with integers the resulting number is filled to this width with leading Zeros.
  • E<exponent> gives the digits of the exponent for scientific notation
  • Format specifiers can be grouped with brackets which may have also a <repeat> field in front.

<Format code> is a usually a single letter:

<Format code> meaning
A alpha numerical string
I Integer: decimal representation
B Integer: binary notation
O Integer: octal notation
Z Integer: hexadecimal notation
F or D Floating point number
e or E Floating point number in Scientific notation with 'e' or 'E' for the exponent
g or G Floating point number in Scientific notation only if necessary
C 'Calendar data' see 'julian day' below

There are further formatting codes → type '? format codes' at the idl prompt. Or look at using explicitly format, format codes fortran or format codes cprintf.

mean, moments

function mean calculates the average of a complete array.

xm = mean( x , /nan )

Flag /nan makes idl to ignore nans. If you also need variance and higher moments use instead moment which does the calculation faster:

m = moment( x, /nan )
x_mean = m[0]
x_var  = m[1]
s_skew = m[2]
s_kurt = m[3]

With version 8 idl can restrict moment and mean to (only) one dimension:

; generate some data
N = 1000
x = randomu(rs,N) ; this gives a fltarr(N)
y = (randomu(rs,N))^2; this gives a fltarr(N)
xy = [ [x], [y] ] ; this gives a fltarr(N,2)
m_x_y = mean(   xy, dimension = 1 ) ; this gives a fltarr(2) equal to [ mean(x), mean(y) ]
m_xy  = mean(   xy, dimension = 2 ) ; this gives a fltarr(N) equal to m_xy[i] = mean([ x[i], y[i] ])
mom = moment(   xy, dimension = 1 ) ; this gives a fltarr(2,4) equal to  [ [moment(x)], [moment(y)] ]

with variable dimension indicating the number of the dimension over which to average. The first dimension has number 1.

The same can be done with the max and min functions.

max_x_y = max( xy, dimension=1 ) ; this gives a fltarr(2) equal to [ max(x), max(y) ]
max_xy  = max( xy, dimension=2 ) ; this gives a fltarr(N) equal to max_xy[i] = max([x[i], y[i]]) 
...

speed

IDL becomes slow if you use loops like for, while or repeat and conditional statments like if or case and even slower if you combine them.

This webpage provides some tips to speed your code: http://www.danidl.co.uk/idl.speed.tips.shtml

You can speed up your code if you use idl routines, matrix operations etc. Some examples below:

running average

We want a running average over 2*M points:

this is slow

M = 11
for i = 0, N-1 do begin
  ia = max([i-M,0])
  ib = min([i+M,N-1])
  xm[i] = mean( x[ia:ib] , /nan )
endfor

use instead

xm = smooth( x, 2*M , /edge_truncate , /nan )

The /nan key word let idl ignore NaN1)

or in two dimensions if you want to average in x direction only.

Mx = 2*M
My = 1
xm = smooth( x, [Mx,My] , /edge_truncate , /nan )

alternatively:

Mx = 2*M
My =  1
kern = make_array( Mx, My, /float, value = 1.0/(Mx*My)  ) ; equal to kern=fltarr(Mx,My) & kern[*]=1.0/(Mx*My)
xm = convol( x , kern , /edge_truncate , /nan )

Adapt Mx and My if you want more … This opens also possibilities to more sophisiticated smoothing techniques. Eg. with a gaussian smooother equivalent to the arithmetic mean over M elements:

; sigma derived from arithmetic average width M
sigma_gauss = M/3.
; width should be at least 3sigma ... we take here 4 sigma
N_gauss = floor(sigma_gauss*4)*2+1
; kernel is gaussian bell 
kern = exp( - 0.5 * ( (findgen(N_gauss)-0.5*(N_gauss-1)) / sigma_gauss )^2 ) 
; nomrlaize it
kern = kern/total(kern)
; convolute
xm = convol( x , kern ,  /edge_truncate , /nan )

a running standard deviation is also easily done

x_sdev = sqrt( smooth( (x-xm)^2 , 2*M , /edge_truncate , /nan ) )

variances, covariances

Instead of for-loops use the variance or moment routines of idl.

var_x = variance(x , /nan )
; or 
moms_x = moment( x , /nan  )
mean_x = moms_x[0]
var_x = moms_x[1]

For the cross covariance use

xm = mean(x,/nan)
ym = mean(x,/nan)
covar_xy = mean( (x-xm)*(y-ym) , /nan )

If your data set is small you should multiply a factor N_data/(N_data-2) where N_data = total(finite(x-xm) AND finite(y-ym))

for the time laged cross covariance use:

N_data = n_elements(x)
xp = x - mean(x,/nan)
yp = y - mean(y,/nan)
N_lag = 10 ; must be smaller than N_data !
covar_xy_pos = fltarr(N_lag) ; for positive lags = indgen(N_lag)
covar_xy_neg = fltarr(N_lag) ; for negative lags =-indgen(N_lag)
for i_lag = 0, N_lag-1 do begin
  covar_xy_pos[i_lag] = mean( xp[i_lag:N_data-1      ] * yp[  0  :N_data-1-i_lag] , /nan )
  covar_xy_neg[i_lag] = mean( xp[  0  :N_data-1-i_lag] * yp[i_lag:N_data-1      ] , /nan )
  endfor ; i_lag

And similar for the auto covariance by replacing y by x.

If you want to evaluate it for N_lag in the order of N_data it is much more time efficient to use the identity2):

cov(x,y) = FFT( FFT(x) * CONJ(FFT(y)), /INVERSE )

but then there must be *not any NaN* in your dataset !
Be aware that:

  • covariances for negative timelags are at the end of the iFFT array.
  • this identity assumes periodicity of the time series i.e. x[i] = x[i+N], … etc.

conditional if's

If you want to use data only if it is above a threshold this will be slow:

for i = 0, N-1 do begin
  if x[i] lt threshold then begin
    x[i] = !values.f_nan
  endif ; x[i]
endfor ; i

use instead:

i_ts = where( x lt threshold , N_ts )
if N_ts gt 0 then x[i_ts] = !values.f_nan

the if N_ts gt 0 is necessary because i_ts will be equal to -1 if no data is found which will result in an error prior to version IDL 8. But IDL 8 treats negative indices as relative indices - so you might not even notice that something wrong happened.

And similar if you do not want to juggle with NaNs create a new dataset:

i_good = where( x gt threshold, N_good )
if N_good gt 0 then x_good = x[i_good] $
               else x_good = [] ; this gives !NULL (a pointer pointing nowhere ..) which may result in strange results

Programs and Routines

have the basic structure:

PRO <name> [,<p1> [,<p2> [,<p3> [...]]]] [,<v1>=<v1> [,<v2>=<v2> [,<v3>=<v3> [...]]]]
...
command block 1
...
END
[...
command block 2
...
END  ]
  • <name> name of the program, it must be identical with the filename, except for the extension.
    The code above must go into the file <name>.pro
  • <p1>,<p2>,… names of parameters
  • <v1>=<v1>,<v2>=… names of named parameters
  • command block 1 will be executed if the program is called
  • command block 2 is optional and will be executed if the program is compiled…

Example:

PRO test ,x ,y ,dings=dings

IF dings THEN PRINT,x,y
END

must be saved as test.pro
and be compiled with

.RUN test

it can then be called with

test,'aha',3.14,/dings
test,'aha',3.14,dings=0
test,'aha',3.14,dings=1
test,'aha',/dings
...

some of these examples will produce errors…

To make parameters optional you should test whether they are present when the programm is called. This can be done with:

IF NOT ARG_PRESENT(dings) THEN dings = 0

ARG_PRESENT checks whether the variable dings was passed to the program
Nearly the same can be achieved with KEYWORD_SET which checks whether a variable exists and whether its value is not zero

IF NOT KEYWORD_SET(dings) THEN dings = 0

standalone executables

We have only a limited number of licenses for IDL (60 but every user needs 6 at once thus 10effective, of these are 6 for idl8.2 and 4 for 6.4). To reduce the time that IDL is blocked you may either build a standalone executable or use batch jobs:

standalone executables

In principle it is possible to generate standalone executables. They run in IDL virtual machine which executes the code 3). Additional the virtual machine always asks for a mouse click - also if your application has no graphical user interface. The advantage is that it does not block a license.

Details how to generate and execute standalones are describe under “The idl virtual machine”:
http://www.physics.nyu.edu/grierlab/idl_html_help/distributing6.html#wp1004661

And a more compact description at idl coyote:
https://www.idlcoyote.com/tips/idl_icon.html

Here is how to do it:

  • assume you have a main program main.pro which you want to run. In IDL execute the following command:
    .COMPILE main  ; compile main program
    RESOLVE_ALL    ; resolve all dependencies ...
    ; RESOLVE_ALL, CLASS=<object_names> ; ... if you use objects provide their names as a strarr in <object_names> 
    SAVE, /Routines, Filename='main.sav' ; generate the ''.sav'' file which may run in the virtual machine
  • in windows you may just click on main.sav to run it
  • in linux call from the command prompt:
    $ idl -vm=main.sav

batch jobs

If you want to run the idl from a bash script you may use an IDL batch job.

An idl program which name starts with a '@' character is interpreted as a batch job. It can be started from the comandline and occupies an idl license only as long it is running. You may edit the code between calls without blocking IDL.

Here is how to do it:

  • assume your idl stuff is done in idl_program.pro
  • write a batch file '@call_idl_program' (you can choose any name, but it must start with a '@' character)
; @call_idl_program
; compile and call idl_program.pro
.COMPILE idl_program ; compile your stuff
idl_program          ; run it
EXIT                 ; leave idl
  • call it from command prompt with
$ idl @call_idl_program

You cannot pass paramters from the command line to your program. You either have to

  • provide paramters in your @call… file, e.g. generate it from your calling script
  • or call an idl program from within the @call… batch job
  • or provide parameters via an input file you read in idl_program.pro

a simple idl program to provide paramters could look like

a = 1
time = julday( 1,1,1970, 0,0,0 )
x = 3.14*a
end

save as set_my_params.pro and in @call_idl_program just include a line

; @call_idl_program
; compile and call idl_program.pro
.COMPILE idl_program ; compile your stuff
set_my_params        ; set parameters
idl_program          ; call your program
EXIT                 ; leave idl

Functions

have in principle the same structure as programs, except that they return a value:

FUNCTION <name> [,<p1> [,<p2> [,<p3> [...]]]] [,<v1>=<v1> [,<v2>=<v2> [,<v3>=<v3> [...]]]]
...
command block
...
RETURN, <variable>

END

Example:

FUNCTION sinx,x
RETURN sin(x)/x
END

Files

text

open (text)files for input into idl:

s = '' ; this line predefines s as a string and the file is read textline by textline

OPENR, file_unit, filename, /Get_LUN
...
  ; read one textline
  READF, file_unit, s
...
CLOSE, file_unit
FREE_LUN, file_unit

if the file is compressed with gzip it can be directly read by setting the flag /compress in OPENR

open (text)files for output from idl:

OPENW, file_unit, filename, /Get_LUN
...
  PRINTF, file_unit, s
...
CLOSE, file_unit
FREE_LUN, file_unit

Read a text file containing a table with known format

OPENR, file_unit, filename, /Get_LUN
...
  READF, file_unit, var_int, var_float, var_sci_float, format='(I3, F7.2, E12.2)'
...
CLOSE, file_unit
FREE_LUN, file_unit

Read a text file containing a table with columns separated by TAB's

  TAB = string(9b)
  OPENR, file_unit, filename, /Get_LUN
  ...
    READF, file_unit, s
    col = STRSPLIT( s , TAB , count=N_col , /extract )
  ...
  CLOSE, file_unit
  FREE_LUN, file_unit
 

Count lines in a text file can be done with function file_lines, filename

read_ascii

The above are simple alternatives to the very complex idl routine READ_ASCII :

data = read_ascii( filename )

will read the data into a strucutre containing one array:

help, data
Structure <1485998>, 1 tags, length=<N_col>*<N_row>*<size_of_float>, data length=<N_col>*<N_row>*<size_of_float>, refs=1:
  FIELD1          FLOAT     Array[ <N_col>, <N_row> ]

it is possible to exclude comments, a header, or read only parts of the file by defining a TEMPLATE.

This can be done with the function ASCII_TEMPLATE which opens an interactive dialog.

template = ASCII_TEMPLATE(  data_file_name )

In principle this function can detect several features of the file (like column separator, data type per column, …) But you have to click several buttons.

The function returns a structure with the following fields:

Structure <179f0e8>, 10 tags, length=120, data length=117, refs=1:
 VERSION         FLOAT           1.00000
 DATASTART       LONG                 0
 DELIMITER       BYTE        <delimiter ascii code>
 MISSINGVALUE    FLOAT               NaN
 COMMENTSYMBOL   STRING    '<comment symbol>'
 FIELDCOUNT      LONG         <N_col>
 FIELDTYPES      LONG      Array[<N_col>]
 FIELDNAMES      STRING    Array[<N_col>]
 FIELDLOCATIONS  LONG      Array[<N_col>]
 FIELDGROUPS     LONG      Array[<N_col>]

Accordingly it should be possible to construct it in the code to avoid calling the dialog:

; delimiter between columns
; delimiter = ',' ; comma for csv files
delimiter = string(9b) ; tab for tsv files

; read header
; the line where to find the column header , we assume here the very first line
i_header = 0
header = '' ; predefine as a string => file can be read textline by textline
OPENR, file_unit, filename, /Get_LUN
; read first textlines until we have the column header
for i = 0, i_header do READF, file_unit, header
;   one may alternatively search for a certain key in the text line with
;   key = '# time'+delimeter
;   N_header = 0
;   WHILE (strpos(header,key) lt 0) and (~ eof(file_unit)) DO BEGIN
;     READF, file_unit, header
;     N_header += 1
;     ENDWHILE ; key in header
;   i_header = N_header-1
; check whether there remains anything in the file
if eof(file_unit) then message, 'no data in file !'
; close file
CLOSE, file_unit
FREE_LUN, file_unit

; split header, and count columns separated by delimiter.
header = strsplit( header, delimiter, /extract, /preserve_null, count = N_col )

; define type of variables in different columns
; type: 0=skip field, 1=byte, 2=int16, 3=long32, 4=single, 5=double, 7=string, ... see idl help for size
; i assume here that the first 3 columns are strings, i.e. cannot be converted into numeric values and the rest can be converted to single precision floating point
N_col_str = 3
fieldtypes = [ replicate(7,N_col_str), replicate(4,N_col-N_col_str)  ]

template = { $
  VERSION         : 1.0, $ ; must be 1.0 ! IDL 6.0 does not know anthing else ...
  DATASTART       : i_header+1 , $  ; index to line where to start reading. First line=0. We assume here that data starts directly after the header
  DELIMITER       : delimiter, $ ; one character - must be defined above e.g. as TAB=string(9b) or comma=',' ...
  MISSINGVALUE    : !values.f_nan, $ ; idl inserts this if it cannot read
  COMMENTSYMBOL   : '#', $ ; one character ...
  FIELDCOUNT      : N_col, $ ; number of columns, number of elements of following fields must fit
  FIELDTYPES      : fieldtypes, $ ; type: 1=byte, 2=int, ... 4=single, 5=double, 7=string, see idl help for size
  FIELDNAMES      : header, $ ; these names will be used for the variables, i.e. entries in header must follow the rules for variable names
  FIELDLOCATIONS  : replicate(  0, N_col ), $ ; indices to column begin, if no delimiter is given (?), or if columns are of constant width
  FIELDGROUPS     : indgen(N_col) $ ; every field an independent group, i.e. one element in the struct data we are going to read
  ; FIELDGROUPS     : replicate(0,N_col) $ ; every field the same group - only possible if the same type ! => data = { header[0] : array(N_col:N_lines) }
  ; FIELDGROUPS     : [ replicate(0,N_col_str) , replicate(1,N_col-N_col_str) ] ,  $ ; one group for the strings, one for the floats => data = { header[0] : strarr(N_col_str,N_lines), header[N_col_str] : fltarr(N_col-N_col_str,N_lines) }
  }

; read the data
data = read_ascii( filename, template=template )

; show info about the structure of your data
help, data, /struct

...
  • FIELDCOUNT and number of elements of FIELDTYPES, -NAMES, -LOCATIONS and -GROUPS must be the same !
  • if you group your data
    • you will get for every group an M x N array with M the number of group members and N the number of records (or lines)
    • you nevertheless need to provide FIELDTYPES, -NAMES, -LOCATIONS and -GROUPS with FIELDCOUNT elements !
    • the name of every group is then the name of the first FIELDTYPENAME going into it

binary

with READU binary data can be read:

...
; define x as long variable
x = 0l
; read it from file
READU, file_unit, x
...

with WRITEU binary data can be written:

...
WRITEU, file_unit, x
...

file size, position etc

check for existence

file_info_rec = FILE_INFO( filename )
if file_info_rec.exists then print,'exists' $
                        else print, 'does not exist'
                        

or in one line

if (FILE_INFO( filename )).exists then print,'exists' else print, 'does not exist'

get file size

file_info_rec = FILE_INFO( filename )
print, 'file size=', file_info_rec.size

set file pointer to a certain position

OPENR, file, filename, /Get_LUN
...
POINT_LUN, file , file_position

get position of file pointer (⇒ negative file unit number!) :

POINT_LUN, -file , file_position

count lines in a text file

N_lines = FILE_LINES( file_name ) 

NETCDF

We prefer to save data as NETCDF files (=Network Common Data Format)
NetCDF is a platform independent binary format which includes meta information about its content.
It can be easily read in many programming platforms like matlab, python, C, …
As a binary format reading is very fast and access to a subset of the content is easy.

I wrote functions nc_read.pro and read_nc_group.pro to read netcdf files into a structure comparable to the Python function xarray.open_dataset.
You can find them in /home/jschween/exp/ideen/idl/netcdf.

For details of IDLs netcdf functions see the IDL help pages for a NCDF Overview.
Below example codes rely on the basic NETCDF functions IDL provided since long. Since version 8.4.1 IDL provides a simplified interface which also provides compact access to netcdf data.

Create a NETCDF file

; create netcdf output file
; use flag /clobber => overwrite exisiting file (the verb to clobber means to hit or punish someone, what strange... )
nc_file = NCDF_CREATE( nc_filename , /clobber )

; set global attributes
NCDF_ATTPUT, nc_file, /Global, 'Creator' , 'Jan Schween (jschween@uni-koeln.de)'
NCDF_ATTPUT, nc_file, /Global, 'creation_date' , STRING( SYSTIME(/utc,/jul), f='(C(CDI02,".",CMoI02,".",CYI04,"/",CHI02,":",CMI02))' )

; define time as dimension
; the corresponding variable shall have the same name as it is intended to be a coordinate variable
N_time_id = NCDF_DIMDEF( nc_file, 'time', N_time )

; we use NaN for missing values
novalue = !values.f_nan

; define time variable with dimension time, 
;   as time is a coordinate variable its name should be the same as the dimension
;   additionally as coordinate variable it should be in ascending order and have no gaps (missing values, filled values)
;   we use here the IDL - time format julian day which allows the determination of calendrial dates or can be inversely determined from calendar dates.
time_id = NCDF_VARDEF( nc_file, 'time', N_time_id, /DOUBLE )
; set attribute for the units of time
NCDF_ATTPUT, nc_file, time_id, 'units' , 'days since -4712-1-1T12:0:0' ; format according to cfconventions, see https://cfconventions.org/Data/cf-conventions/cf-conventions-1.7/build/ch04s04.html , this should work with   "ncdump -iv time <nc_file>"   ... not allways but sometimes(?) ...  
NCDF_ATTPUT, nc_file, time_id, 'calendar', 'gregorian' ; this is the standard calendar - in principle you can omit this.
NCDF_ATTPUT, nc_file, time_id, 'long_name', 'time as julian day'
NCDF_ATTPUT, nc_file, time_id, 'missing_value', double(novalue)
NCDF_ATTPUT, nc_file, time_id, '_fillvalue', double(novalue) ; allthough unidata gives "_FillValue" with capital letters, you may get an error when using them... (see [[http://www.rhinocerus.net/forum/lang-idl-pvwave/575253-ncdf_attput-_fillvalue-problem-string-arrays.html|here]])
NCDF_ATTPUT, nc_file, time_id, 'comment', 'Julian day are the fractional days since 1.1.4713BC 12GMT. It can be converted to unix epoch (t_unix:seconds since 1.1.1970) with t_unix=(time-2440587.5)*86400.'

; alternativley time as unix epoch - wide spread time format usually of type integer, but then with overflow problem...
tunx_id = NCDF_VARDEF( nc_file, 'tunix', N_time_id, /LONG ) 
NCDF_ATTPUT, nc_file, tunx_id , 'units' , 'seconds since 1970/1/1 0:0:0'  ; format according to cfconventions, , evtly. '-' as separator in date? see https://cfconventions.org/Data/cf-conventions/cf-conventions-1.7/build/ch04s04.html, no time zone info means automatically UTC

; create id for a variable dependent on time, 
; possible types are: /BYTE , /CHAR, /DOUBLE , /FLOAT , /LONG , /SHORT, /STRING, /UBYTE, /UINT64, /ULONG, /USHORT
; where /SHORT is a two byte integer, /LONG is a 4byte integer, and /UINT64 is an unsigned 8byte integer,
; and you may already guess: /U... means that the variable is an unsigned integer
var_id = NCDF_VARDEF( nc_file, 'some variable name', N_time_id, /FLOAT )
; multidimensional arrays like fltarr(N_time,N_space) are defined via
var_2d_id = NCDF_VARDEF( nc_file, '2D variable', [ N_time_id, N_space_id ], /FLOAT )
; single vars are defined by omitting the dimension
var_one_value_id = NCDF_VARDEF( nc_file, 'one_value', /FLOAT )

; set attributes
; these here are standard attributes (including the underscore) as defined in the unidata conventions ...
NCDF_ATTPUT, nc_file, var_id, 'long_name', 'long name'
NCDF_ATTPUT, nc_file, var_id, 'units', 'arb.units'
NCDF_ATTPUT, nc_file, var_id, 'missing_value', novalue
NCDF_ATTPUT, nc_file, var_id, '_fillvalue', novalue ; allthough unidata gives "_FillValue" with capital values, you may get an error when using them... (see [[http://www.rhinocerus.net/forum/lang-idl-pvwave/575253-ncdf_attput-_fillvalue-problem-string-arrays.html|here]])



; further variables and attributes ...
; ...

; STRINGS are stored as array of (unsigned) bytes called CHAR
; we therefore need the length of the string
; and the netcdf vaiable has to dimensions:
; lets assume we have str_var = strarr(N_str_var) 
max_str_len = max( strlen(str_var) )
; define dimension
str_len_id = NCDF_DIMDEF( nc_file, 'str_len', max_str_len )
str_var_id = NCDF_VARDEF( nc_file, 'str_var_name', [ str_len_id, N_str_var_id ], /CHAR )

; close define mode
NCDF_CONTROL, nc_file, /endef

; put variable into file ...
NCDF_VARPUT, nc_file, time_id, time
NCDF_VARPUT, nc_file, tunx_id, (time-2440587.5)*86400.

; STRING variable is as simple as the others ...
NCDF_VARPUT, nc_file, str_var_id, str_var
  
; close netcdf file
NCDF_CLOSE, nc_file

NETCDF time.units

For time.units I follow here cfconventions.
But they are not very clear - they just give examples which differ from each ohter.
Accordingly time.units could have the form:

  • '-' as separator in date
    • <base_unit> since <[-]YYYY-MM-DD hh:mm:ss>
  • clock time with no leading zeros
    • <base_unit> since <[-]YYYY-MM-DD [h]h:[m]m:[s]s>
  • '/' as separator in date
    • <base_unit> since <[-]YYYY/MM/DD hh:mm:ss>

Commandline tool ncdump cannot deal in every case with the clock-time in time.units.
what seem to work with ncdump is:

  • you need dashes '-' as separators in date
  • blank ' ' or 'T' as separator between date and time
  • Leading zeros can be omitted
  • that means
    • [-][Y[Y[Y]]]Y-[M]M-[D]D{ |T}[h]h:[m]m:ss

Dimensions

In the above example 'time' is the name of the dimension and of a variable. That is intended to make clear the close relation between N_time and time = dblarr(N_time) and part of the CF netcdf dataformat4). But it is also possible to use different names, in this case ncview allows you to plot an array against other variables…

In netcdf 64 it is possible to have one 'UNLIMITED' dimension. I.E. this dimension does not need to be specified at creation time and data can be appended to the file. In idl this is done by using the keyword '/unlimited' (see also the example 'appending data …' below):

id_dim_time = NCDF_DIMDEF( nc_file, 'time', /unlimited )

Attributes

There is a list of netcdf attribute conventions at the unidata pages

possible datatypes in netcdf:

IDL help is not 100% clear about the meaning of the datatypes in netcdf :

  • /BYTE 8bit signed/unsigned(?) integer … ncview assumes signed i.e. numbers between -128 and 127
  • /CHAR this is also byte, but assumed to be characters i.e. a string can be stored as an array of char but you have to convert it in idl to string. Might be also interpreted as unsigned 8bit integers i.e. numbers between 0 and 255
  • /SHORT 16bit integer (2 byte) probably signed, i.e. numbers between -32768 and +32767
  • /LONG longword integers i.e. probably 32 bit signed integers i.e. numbers from − 2,147,483,648 to 2,147,483,647
  • /FLOAT floating point numbers - probably IEEE single precision (32bit)
  • /DOUBLE IEEE double precision (64bit)

Read a NETCDF file:

; open netcdf file
nc_file = NCDF_OPEN( filename , /NOWRITE )

; get global attribute
NCDF_ATTGET, nc_file, /GLOBAL, att_name, glob_att

; get id of dimension
dim_id = ncdf_dimid( nc_file , 'dim_name' )

; get id of variable via its name
var_id = NCDF_VARID( nc_file, 'some variable name' )

; get attribute for variable
NCDF_ATTGET, nc_file, var_id, 'units', var_unit_str

; get dimension
NCDF_diminq, nc_file , dim_id, name_dim , N_dim 

; elements of a metadata group
; see on Harris
;   http://www.harrisgeospatial.com/docs/ncdf_ncidinq.html
;   http://www.harrisgeospatial.com/docs/ncdf_groupname.html#netCDF_2618656010_1006782
metadata_grp_id = NCDF_NCIDINQ( nc_file , Metadata_group_name )
mata_data_id = NCDF_VARID( metadata_grp_id, meta_data_name )

; get data
NCDF_VARGET, nc_file, var_id, variable

; get string data
str_var_id = NCDF_VARID( nc_file, 'str_var_name' )
NCDF_VARGET, nc_file, str_var_id, str_var
str_var = string( str_var )

; close netcdf file
NCDF_CLOSE, nc_file

appending data to an exisitng netcdf file:

Appending data is possible if the dimension is defined as unlimited. Defining the variables is as usual with one exception:

; define unlimited dimension time 
id_dim_time = NCDF_DIMDEF( nc_file, 'time', /unlimited ) ; only one inlimited dimension allowed (?)
id_dim_2D = NCDF_DIMDEF( nc_file, 'N_2D', N_2d )
; connect variables to it
id_time = NCDF_VARDEF( nc_file, 'time', id_dim_time , /DOUBLE)
id_data = NCDF_VARDEF( nc_file, 'data', id_dim_time , /FLOAT )
id_data_2D = NCDF_VARDEF( nc_file, 'data', [ id_dim_2D, id_dim_time ], /FLOAT ) ; for 2D data the unlimited dimension must be at the end

If the file remains open just append the data at the desired position

; append data assuming that variables //time// and //data// contain only new data
NCDF_VARPUT, nc_file, id_time, time, offset=[ N_time ]
NCDF_VARPUT, nc_file, id_data, data, offset=[ N_time ]
NCDF_VARPUT, nc_file, id_data_2D, data, offset=[ 0, N_time ]

If the file is reopened the number of elements already in the file must be determined:

nc_file = NCDF_OPEN( nc_file_name , /write )
; get id for variables time and data
id_time = NCDF_VARID( nc_file , 'time' )
id_data = NCDF_VARID( nc_file , 'data' )

; get number of elements in time in two steps
info_time = NCDF_VARINQ( nc_file , id_time )
NCDF_DIMINQ, nc_file, info_time.dim[0], name, N_time

; alternatively
id_time = NCDF_DIMID( nc_file , 'time' )
NCDF_DIMINQ, nc_file, id_time, name, N_time

; append data assuming that variables //time// and //data// contain only new data
NCDF_VARPUT, nc_file, id_time, time, offset=[ N_time ]
NCDF_VARPUT, nc_file, id_data, data, offset=[ N_time ]

Compression

With NETCDF 4 it is possible to compress data (see unidata.ucar.edu). IDL can read NETCDF4 file as of version 8.0 (see IDL ref pages for NCDF_CREATE). You can check the NETCDF version implemented in IDL with HELP, 'ncdf', /DLM.

With our IDL9 implementation this does not work anymore: you get an netcdf error -31 after NCDF_CREATE !?!?
Although it say it has version 4 implemented:

IDL> help, 'ncdf' , /DLM
** NCDF - IDL Network Common Data Format (NetCDF) support DLM (loaded)
  Version: netcdf-c 4.9.2, Build Date: NOV  8 2023, Source: NV5 Geospatial Solutions, Inc.
  Path: /opt/nv5/idl90/bin/bin.linux.x86_64/idl_netcdf.so

To use NETCDF compression you need to set the NETCDF4_FORMAT keyword when generating the file,
and the GZIP and SHUFFLE keywords when defining a variable, eventually also CHUNK_DIMENSIONS :

; create netcdf output file
nc_file = NCDF_CREATE( filename , /clobber, /NETCDF4_FORMAT )
...
g_zip_level = 5 ; a value between 0..9 for low...high compression but also fast...slow access
; chunk dimensions: can help the compression but can also be omitted
cd_time  = N_time / 4 ; i guess that is the way to use it
cd_range = N_range / 4 ; i guess that is the way to use it

; get id for a variable dependent on time, 
; possible types are: /BYTE , /CHAR, /DOUBLE , /FLOAT , /LONG , /SHORT
data_id = NCDF_VARDEF( nc_file, 'data', N_time_id, /FLOAT , GZIP=g_zip_level, CHUNK_DIMENSIONS=cd_1, /SHUFFLE )
; multidimensional arrays are defined via
data_2d_id = NCDF_VARDEF( nc_file, 'data_2d', [ N_time_id, N_range_id ], /FLOAT , GZIP=g_zip_level , CHUNK_DIMENSIONS=[cd_time, cd_range ], /SHUFFLE )
...
  • GZIP determines the compression rate. Allowed values are 1..9 for lowest to highest compression or fastest to slowest compression (see help for NC_VARDEF#GZIP).
  • CHUNK_DIMENSIONS determines how large blocks are compressed, it is by default the whole size of the variable except for variable of undefined length (see help for NC_VARDEF#CHUNK_DIMENSIONS). This means you can omit it. I dont know whether this paramter can improve compression.
  • SHUFFLE tries to sort bytes before compression (see help for NC_VARDEF#SHUFFLE).
  • with just GZIP=9 i achieve compression ratios around only Size_comp/Size_org = 85% for some smooth data, when appliying /SHUFFLE this improves to ~40%

Netcdf-groups

Data in Netcdf files can be organized in groups.

Sample code:

; open file
file_Id = NCDF_OPEN(fileName)
; get list of goup-id's
group_id_list = NCDF_GROUPSINQ( file_id )
for i = 0, n_elements( group_id_list )-1 do begin
  group_id_i = group_id_list[i]
  group_name_i = NCDF_GROUPNAME( group_id_i )
  print,  i, '->', group_id_i, '->', group_name_i
  ; in principle a group can again contain groups ...
  ; note that we now use the groups_id to reference the parent group where we used before the file id.
  sub_group_id_list = NCDF_GROUPSINQ( group_id_i )
  ... 
endfor ; i

Rules for variable names in netcdf files:

One idea of netcdf is to read a file directly and automatically into variables with names equal to the names in the file. That means that netcdf-variable names have to follow certain rules and not every character is allowed in a variable name in an netcdf file.

In general: a valid variable name in a programming language like fortran is an allowed variable name for a netcdf file. There is an [incomplete] description of the rules at the Unidata pages:

A valid identifier can be:

ID = ([a-zA-Z0-9_]|{UTF8})([^\x00-\x1F\x7F/]|{UTF8})*

which says:

  • the first char must be alphanumeric, an underscore, or a UTF8 multibyte encoded char.
  • subsequent characters may be anything except control characters [\x00-\x1F] , forward slash [/], and del [\x7F]. So the excluded ascii decimal values are 0-31, 47, and 127.
  • A UTF-8 multibyte encoding always has the high order bit set …

to be complete: the following are not allowed as well:

  • '(' and ')'
  • '/'
  • '%'
  • white space
  • ':' (colon)
  • '“' (double quotes)
  • '*' '+' … (operators)
  • etc. …

The reason for this strict variable naming is that the variable names from inside the file should also be used as variable names in a program. Reading would be thus very simple: just read the file and you have the variables with the same name as in the file. Unfortunately this is not impelemented in idl…

Plot

PLOT, <x-array>, <y-array>

generates a rectangle (box) with automatically scaled abscissa (bottom) and ordinate (left side) and plots <y-array> versus <x-array>. There are many optional parameters and many addtional keywords to modify the plot.

The scaling is by default something like 10% wider than the range of the data but this can be modified by the style keyword. You set essentially single bits of it and may combine (at least some of) them:

[x|y|z]style
1 -> Force exact axis range.
2 -> Extend axis range.
4 -> Suppress entire axis
8 -> Suppress box style axis (i.e., draw axis on only one side of plot)
16 -> Inhibit setting the Y axis minimum value to 0 (Y axis only) 

suppress entire x-Axis (top and bottom of the box) with style option

xstyle=4

suppress top axis and right y-axis

xstyle=8, ystyle=8

Suppress annotation of axis-ticks

xtickname=replicate(' ',30)

Die Achseenbeschriftung besteht aus dreisssig Leerzeichen, IDL beschriftet die Achse nicht …
… oder multiplot.pro von mir verwenden.

The rectangle around a plot is in idl called AXIS - there is a command AXIS to just define this rectangle without plotting it. But there is no way to return to a formerly defined one.

Contour plots (isolines and/or color shading ) can be done with the command countour:

CONTOUR, z, x, y

I takes in principle the same options and keywords as the plot command plus some other.

Polar contours can be done with:

POLAR_CONTOUR, field, az_list, R_list

ticks

you can define the length of intervals between ticks and the number of sub intervals with {x|y}tickinterval and {x|y}minor where minor gives the number of subintervals - not ticks ! If you want to have ticks every 2.5 units and minor ticks every 0.1 then:

plot, ... , xtickinterval = 2.5, xminor=5

or

!X.xtickinterval = 2.5
!X.minor = 5

if you do not want minor ticks set

xminor = 1

Annotation

you may want to annotate an axis with your own words. You then have to set {x|y}ticks with the number of intervals (not ticks !) {x|y}tickname with an array of strings for the annotations and {x|y}tickv for the locations where to put the tics. Note that {x|y}ticks is the number of intervals i.e. n_elements({x|y}tickname)-1. If you omit xticks IDL assumes 5 intervals or 6 ticks !.

Here is an example with a logarithmic time axis:

hours_per_day = 24d
days_per_year = 365d

; basic unit of our x-axis is years - thus it is:
hour = 1./(hours_per_day*days_per_year)
day =  24*hour
month = 30*day  ; ... well roughly - average length of a month is 30.416.. and 30.5days for a normal and a leap year, respectively, or ~30.44days on average

x_tikvals = [ hour,   day,  month,   1.,   10. ,  100. ,  1000.,  10e3,   100e3,   1e6   ]
x_tiknams = [ '1h',  '1d',  '1Mo',  '1y', '10y', '100y',  '1ky', '10ky', '100ky', '1My'  ]
N_ticks = n_elements(x_tiknams)

plot, [1], [1], $
  xstyle = 1, $ 
  xrange = [ 0.5 * hour, 2*1e6 ], $
  /xlog, $
  xticks = N_ticks-1, $  ; holy IDL - why do you need things like this ?
  xtickname = x_tiknams, $
  xtickv    = x_tikvals, $
  ystyle  = 1, $
  yrange = [ 0.5, 4.5 ], $
  yminor = 1, $
  /nodata

Similar for an x-axis based on julian day:

hours_per_day = 24d

; basic unit of our x-axis is (julian)days - thus it is:
hour = 1./hours_per_day
day =  24*hour
month = 30*day  ; ... roughly: average length of a month is 30.416.. and 30.5days for a normal and a leap year, respectively, or ~30.44days on average
year = 365.*day ; ... roughly ...
minute = hour/60.
second = minute/60.
milisec = second/1000.

x_tikvals = [ milisec, second, minute,  hour ,  day ,  month,    year   ]
x_tiknams = [ 'msec',   'sec',  'min', 'hour', 'day', 'month',  'year'  ]
N_ticks = n_elements(x_tiknams)

plot, [1], [1], $
  xstyle = 1, $ 
  xrange = [ 0.5*x_tikvals[0], 2*x_tikvals[N_ticks-1] ], $
  /xlog, $
  xticks = N_ticks-1, $  ; holy IDL - why do you need things like this ?
  xtickname = x_tiknams, $
  xtickv    = x_tikvals, $
  ystyle  = 1, $
  yrange = [ 0.5, 4.5 ], $
  yminor = 1, $
  /nodata

Annotation of a time axis

IDL provides in procedure PLOT the parameter XTICKUNITS which allows automatic annotation of a time axis. If t is time in julianday then

plot, t, x, XTICKUNITS = 'Months'

generates a plot where every first of a month a tick is set. Annotations are the three first letters of the name of month (format = 'CMOA' )

Other possible values for XTICKUNITS are:

  • 'Days'
  • 'Hours'
  • 'Minutes'
  • 'Seconds'

A tick is set in fixed intervals optimal for the length of the axis (N days, or N hours etc.). Annotation is the day of the month or hour of day etc. but can be formatted with XTICKFORMAT. For example:

plot, t, x, XTICKUNITS = 'Days', XTICKFORMAT = '(C(CDI02,",",CMOI02))'

Will give annotations of the form DD.MO instead of the month name.

Another example:

plot, t, x, XTICKUNITS = 'Hours', XTICKFORMAT = '(C(CHI02,":",CMI02))'

Will give annotations of the form HH:MM instead of just hour of the day as one or two digit number.

Another way is to explicitly define format and intervals, e.g. :

plot , t, y , XTICKFORMAT = '(C(CHI02,".",CMI02,"."))', XTICKINTERVAL = 3/24., XMINOR = 3

annotates HH.MM every 3 hours (=3/24. of a day) and generates two minor ticks for 3 subintervals i.e. minor ticks at full hours.

You may want to have annotations in two lines: use code !C for a newline

plot, t, y, XTICKFORMAT = '(C(CDI02,".",CMoI02,"!C",CYI04))'

xstyle: axis layout

{x|y|z}style = 
 1 => force exact range
 2 => extended range
 4 => supress entire axis (dont draw line and tic marks ...)
 8 => supress box style - axis only one one side of plot
16 => supress y=0 value for y axes - you can also use the /YNOZERO flag in the plot command...

linestyles

linestyle = 
 0 => Solid
 1 => Dotted 
 2 => Dashed 
 3 => Dash Dot 
 4 => Dash Dot Dot 
 5 => Long Dashes 

symboltypes

PSYM =
 1 => Plus sign (+) 
 2 => Asterisk (*) 
 3 => Period (.) 
 4 => Diamond 
 5 => Triangle 'up'
 6 => Square 
 7 => X 
 8 => User-defined. See USERSYM procedure. 
 9 => Undefined 
10 => Histogram mode. Horizontal and vertical lines connect the plotted points, 
      as opposed to the normal method of connecting points with straight lines. 
      Vertical lines are located in the middle between subsequent x.
      See Histogram Mode for an example. 
      

Size of the symbol is controlled via the symsize keyword. A value of one is about one charactersize.

IDL does not know filled symbols, but one can use alternatively the userdefined symbol PSYM=8 and the USERSYM procedure. To generate a filled triangle pointing to the top use:

  N_sym = 3
  w0_sym = 0.
  w_sym = findgen(N_sym+1)/N_sym*2*!Pi
  usersym, sin(w_sym+w0_sym) , cos(w_sym+w0_sym), /fill
  plot, x , y, psym=8

To make different symbols set:

symbol N_sym w0_sym
triangle 'up' 3 0
triangle 'right' 3 !pi/2
triangle 'down' 3 !pi
triangle 'left' 3 -!pi/2
diamond 4 0
square 4 !pi/4
circle 15 0

More complex symbols like stars might be generated by using something like

usersym, sin(w_sym*N_arms)*sin(w_sym+w0_sym) , sin(w_sym*N_arms)*cos(w_sym+w0_sym), /fill

Or you use programs from the internet like plotsym.pro from http://idlastro.gsfc.nasa.gov/

multiple data rows

To plot multiple data rows use the OPLOT command:

  ; plot y versus x
  PLOT, x, y
  ; plot y2 versus x on same axis
  OPLOT, x, y2

multiple axes

If you want to use several different axes use the AXIS command. To plot some data y and y2 on the same x-axis but on the different y-axes on the (standard) left and the right side use the following code:

  ; define test data
  N = 1000
  x = findgen(N)/N
  y = sin(2*!pi*x)
  y2= 100.*exp(-((x-0.5)/0.1)^2)
 
 
  ; plot y data on (standard) left y-axis ...
  ; ... leave additional space of 10 charwidths for x-axis-labels on both sides
  PLOT, x, y1, ytitle='y1', XMARGIN=[10,10]
  ; define second y-axis on the right side , and save it for use
  AXIS, yaxis=1, yrange=[min(y2),max(y2)], ytitle='y2', /save
  ; plot data y2 onto this axis
  OPLOT, x, y2

The keyword yaxis=0 refers to a y-axis on the left side of the plot with tics poiting to the right side (i.e. inward). The keyword yaxis=1 refers to an y-axis on the right side of the plot with tics poiting to the left side (i.e. inward). Nevertheless the position of the axis can be set by giving the x-position of the axis. Here is an example for a third axis:

  ; additional data...
  y3 = atan(10.*(x-0.3))/(!pi/4.)
  ; position for third y-axis - left to the first axis
  x_icpt = -0.13
  ; define third y-axis placed at x=x_icpt, and save it for use
  AXIS, x_icpt, yaxis=0, yrange=[min(y3),max(y3)], ytitle='y3', /save
  ; plot data y3 onto this axis
  OPLOT, x, y3

Of course you can define additonal x-Axes you then have to use the parameter XAXIS=1 (Xaxis on top) or XAXIS=0 (x-axis at the bottom).

Sometimes the title/label for the second axis is not readable (especially if you are creating .ps-plots). An easy solution is to type: !p.position=[0.1,0.1,0.9,0.9] in front of the plot-commands. (Stefan) … More elaborated is to use the XMARGIN keyword in the plot command (added in the example above (Jan)) - or by setting it globally with:

  !X.MARGIN = [10,3]

X margin gives the width of the margin in characters on the left (XMARGIN[0]) and right (XMARGIN[1]) side of the plot axis. Similarily the bottom and top margins can be set with

  !Y.MARGIN = [4,2]

or with the YMARGIN keyword in the plot command.

multiple plots

To get multiple sub plots arranged in a matrix use !P.multi :

!P.multi = [ $
  N_remaining , $ ; number of plots remaining on page , a zero starts a new page
  N_column , $ ; number of columns
  N_row , $ ; number of rows
  N_z_stack , $ ; number of plots stacked in z dimension
  top_2_bottom $ ; Flag: first fill columns before proceeding to next, default is to first fill rows ('column major')
   ] 

Usage:

!P.multi = [ 0 , 2, 3 ]

plots come in a new (erased) 2×3 array, next plot will go into top left position, following one in top row second column, etc.

!P.multi = [ 0 , 5, 5, 1, 1 ]

plots come in a 5×5 array, next plot will go into top left position, next one in second row, first column, etc.

!P.multi = [ 0, 3, 3 ]
...
!P.multi = [ 5 ]

Next plot goes into row 2 colomn 2

Textausgabe in Grafik

XYOUTS, X, Y, String, alignment=alignment, charsize=charsize, width=width, clip=clip, noclip=noclip, ...

print string at pos x,y

  • x,y in data coordinates (axes) - but that can be changed with flags /normalized or /device
    • omitting x,y position will be last (string?) position. in a new plot this is at 0,0 normalized coordinates (i.e. lower left corner)
  • alignment is a floating point number:
    • 0.0 : left bounded, i.e. lower left corner of text is at x,y or text starts at x,y
    • 0.5 : centered, i.e. center of text at baseline is at x,y
    • 1.0 : right bounded, i.e. lower right corner is at x,y
    • yon can also give numbers below 0 or above one - if you know what to do with that
  • charsize is size of character in multiples of character base size (independent of !P.charsize)
  • charthick is linethicknes of drawn fonts (Hershey…)
  • width returns the width of the plotted text in normalized coordinate units, if no other parameter is given the text is not printed.
  • clip=[x0,y0,x1,y1] defines a rectangle where to clip the output text. You need to set noclip=0 to take effect
  • noclip a flag that specifies whether the current clipping region (→ parameter clip or !P.clip) is used, default is noclip=1 i.e. you have to give noclip=0 to take clipping effect.
  • more details xyouts@harris
  • it might be useful to give positions in units of character height or width they can be determined from the system in device coordinates:
; char height and (average) width in device coordinates 
ch = float(!D.y_ch_size) 
cw = float(!D.x_ch_size) 
; ... in normalized coordinates  
ch = float(!D.y_ch_size) / !D.y_size
cw = float(!D.x_ch_size) / !D.x_size
; ... in data coordinates (only in linear coordiante system !)
ch = float(!D.y_ch_size) / (!D.y_size * !Y.S[1])
cw = float(!D.y_ch_size) / (!D.y_size * !X.S[1])
  • it seems as if for pixel based devices it is !D.y_ch_size = 12 pixel. To achieve a certain charsize q_ch relative to bitmap size do:
; charsize in normalized units
; ch = !P.charsize * float(!D.y_ch_size) / !D.y_size
; I want ch = q_ch% <=> q_ch = charsize * float(!D.y_ch_size) / !D.y_size * 100
; => charsize = q/100. * !D.y_size / float(!D.y_ch_size)
q_ch = 3. ; percent of bitmap height
!P.charsize = q_ch/100. * !D.y_size / float(!D.y_ch_size)

Symbol in Grafik

Um ein einzelnes Symbol an bestimmter Stelle in einem Plot zu positionieren:

PLOTS,  xpos, ypos , psym=<symbol id>, ... 

Fonts

there are three font systems in IDL:

  • !P.Font = -1 Hershey-Vector Fonts (scalable and rotatable Vectorfonts, default of idl)
  • !P.Font = 0 'device Fonts' - in the Postscript device these are Postscript Fonts - they are better readable than true-type fonts
  • !P.Font = 1 True Type Fonts (TTF if installed). With TTF some of the below described formatting stuff is not possible - I (jan) find them rather 'pixelig'

Select font

IDL provids the possibility to change the font within the xyouts routine or the [x,y,z]title of plots.

  • put an escape sequence of the form !<command> in the string to be printed
  • different fonts can be selected with !<digit> here comes a selection
    • !3 is the Hershey Helvetica Font which is the default. IDL calls it “simplex Roman” - obviously the people at IDL mixed up the names
    • !6 is the Hershey Font “complex Roman” where every lines is made 3times to make the letters more bolder.
    • !4 is the Hershey Font “simplex Greek” greek letters single line: greek letters in principle in standard encoding, i.e. a ist alpha etc. but q instead of r is rho … there is somewhere a shift in the encoding table! (see "Using vector fonts" ).
    • !7 is Hershey Font “complex Greek” greek letters double lines
    • !9 oder !M is “special Math” - Integral and other special Math-signs this font is used for only one letter
    • !X returns to the 'Entry font'
    • for examples of the available fonts see IDL help under Idl help "Using vector fonts". There you will also see the encoding. You can use the indices from this tables directly. To e.g. print the degree sign which you find in the default font 3 under octal '260' use:
      xyouts, x,y, '15'+string("260b)+'C'
      (the notation is correct: quotation marks do not close and b stands for ocal !)5)

Examples with simplex greek

  • a small greek phi
    !4f!X (take font greek simplex (!4) set an f return to default font (!X))
  • a large greek phi
    !4F!X

You may also use the complex greek (2lines per stroke to make letter more bold)

  • a greek psi
    !7w!X

One may define constants for the different letters:

c_greek_font = '!4' ; simplex greek
;c_greek_font = '!7' ; complex greek
c_alpha   = c_greek_font+'a!X'
c_beta    = c_greek_font+'b!X'
c_gamma   = c_greek_font+'c!X'
c_delta   = c_greek_font+'d!X'

c_epsilon = c_greek_font+'e!X'
c_zeta    = c_greek_font+'f!X'
c_eta     = c_greek_font+'g!X'
c_theta   = c_greek_font+'h!X'

c_iota    = c_greek_font+'i!X'
c_kappa   = c_greek_font+'j!X'
c_lambda  = c_greek_font+'k!X'
c_mu      = c_greek_font+'l!X'

c_nu      = c_greek_font+'m!X'
c_xi      = c_greek_font+'n!X'
c_omikron = c_greek_font+'o!X'
c_pi      = c_greek_font+'p!X'

c_rho     = c_greek_font+'q!X'
c_sigma   = c_greek_font+'r!X'
c_tau     = c_greek_font+'s!X'
c_upsilon = c_greek_font+'t!X'

c_phi     = c_greek_font+'u!X'
c_xi      = c_greek_font+'v!X'
c_psi     = c_greek_font+'w!X'
c_omega   = c_greek_font+'x!X'

c_infinity= c_greek_font+'y!X'

; usage
xyouts, 0,0, c_phi+'('+ c_theta+') = '+ c_delta

Hershey-Vector Fonts

Hersehy vector fonts are one of the oldest computer fonts. They were developed in the late sixties by A.V. Hershey, see wikipedia, Paul Bourke or Idl help "Using vector fonts". They were designed for a plotter i.e. a device which draw lines with a pen. The letters consist solely of polygons or straight lines. To achieve bold(er) letters two or three lines are drawn close to each other. The respective fonts are called then duplex or triplex respectively. For examples see https://www.nv5geospatialsoftware.com/docs/Using_Hershey_Vector_Fon.html. To find out the byte code of the symbols shown in the tables add the (decimal) values at the left and top of the respcetive rows and columns. To get e.g. an $\alpha$ , i.e. the symbol in the row labelled with 96 and the column labelled with 1 you can use string(byte(96+1)).

postscript fonts

setting the post script device up with

SET_PLOT,'ps' ; use postscript device
!P.font = 0 ; use postscript device fonts 
DEVICE, /ENCAPSULATED, /portrait, /COLOR, /HELVETICA , filename='test_font.eps'

; set some font indices (overwrite the original ones )
; index must be in [3..19]
; odd indices are normal , even are italic/oblique
; we need one without serifs (helvetica) one with (times) a typerwriter (courier) and symbol 
DEVICE, /HELVETICA ,           FONT_INDEX=3
DEVICE, /HELVETICA , /OBLIQUE, FONT_INDEX=4
DEVICE, /TIMES ,               FONT_INDEX=5
DEVICE, /TIMES ,     /ITALIC,  FONT_INDEX=6
DEVICE, /COURIER ,             FONT_INDEX=7
DEVICE, /COURIER ,   /OBLIQUE, FONT_INDEX=8
DEVICE, /SYMBOL ,              FONT_INDEX=9 

; char height in normalized coordinates
ch = float(!D.y_ch_size) / !D.y_size

; some letters...  
abc_rst = 'abcdefghijklmnopqrst...'
; character size factor
cs = 2.5
; sample output: switch to font #4 with !4 and back to 'standard' with !X
; gor through fonts 3-9 defined above
for i = 3, 9 do begin
  si = string(i,f='(I0)')
  s = '!'+si+'font #'+si+':'+abc_rst+'!X'
  print, s
  xyouts, 0.01, 1-(i-3+1)*cs*ch, s, charsize=cs, /normal
endfor

device, /close

see the end of the Device fonts section.

type setting

You may generate super- and sub-scripts and further primitve type setting command based on Grandle and Nystrom (1980)6). This is also used in other software packages see e.g. the documentation of NAG e.g. here or here — sorry all the links before are dead. Here are some still alive NAG docs pages: NagText, NAGText@gatech.edu, NAGGraph@gatech.edu.

The following commands are available (see also IDL help Embedded Formatting Commands)7):

  • supersuperscript !U ('Up' faktor 0.62)
  • subscript !D ('Down' faktor 0.62)
  • subsubscript !L ('Low' faktor 0.62)
  • index !I (faktor 0.44)
  • exponent !E (faktor 0.44)
  • back to Normal !N
  • numerator position !A ('above' division line)
  • denominator position !B ('below' division line)
  • new line !C ('carriage return')
  • special characters !Z(b1[,b2[,…]]) where b1,b2,… are the 16 bit hexadecimal unicode codes of the desired character[s]
  • Save position !S
  • Restore Position !R
  • exclamation mark !!

Most of these commands only affect the vertical position except for !R which restrores also the horizontal position.

Examples:

  • super and subscripts
    • $A^b$ (A to the power of b)
      A!Eb!N \\(A !Exponent b !Normal )
    • To get both super and sub script directly after your letter you need to save the horizontqal position:
      $A^b_i$ (A superscript b index i)
      A!S!Eb!N!R!Ii!N
      (A !Save !Exponent b !Normal !Restore !Index !Normal)
      If super and subscript have different lengths you first need to set the shorter one.
  • X-axis Annotation in two lines: use code !C for a newline
  plot, time, y, $
    xtitle = '!CDate (!S!Add.mm!R!B YYYY !N)', $
    xtickformat='(C(CDI02,".",CMoI02,"!C",CYI04))', $
    ...
  • the xtitle starts with a newline (!C) because IDL does not recognize that the annotations have two line now.
    !S!A saves the position after the ”(“ and writes “dd.mm” as nominator,
    !R!A returns to the position after ”(“ and writes the denoniator
    !N ends the nominator and returns to normal before the closing ”)“ is set.
    This generates something like:
  ------+-----------+----------> 
      24.12       24.01
      2019        2020
  • something similar can be achieved by using xtickformat='(C("!S!A",CDI02,".",CMoI02,"!R!B",CYI04,"!N"))' ... with no centering of the YYYY content
  • special characters:
    • the degree sign:
      • a small letter 'o' in the exponent: $15^oC$ : 15!Eo!NC
      • degrees as the special character with octal code 260: string("260b)
      • via its decimal code 176: string(byte(176))
      • or via its hexcode 0xB0: !Z(B0)
  • special characters: greek letters in simplex greek and complex greek
    • $\beta$: !4b!X (switch to simplex greek (!4), draw b, go back to previous font (!X))
    • $\beta$: !7b!X (switch to complex greek (!7), draw b, go back to previous font (!X))
      note that encoding of the greek letters follows only for the four first letters the latin alfabet i.e. a hives $\alpha$ b give $\beta$, but r gives not $\rho$ you need q instead, and s not $\sigma$, you need t instead.
  • cdot (center dot for multiplication)
    • $\cdot$: '!9'+string(byte('056'O))+'!X'
      select special and math font (!9), select the cdot symbol (string(byte(…))), return to previous font (!X)
  • integral sign with integration boundaries
    • $\int_a^b dx$ : '!9'+string(byte(105))+'!X!S!Da!N!R!Ub!N dx'
      select special math font (!9), integral sign (string(byte(105))), restore previous font (!X), save position (!S), lower boundary (!Da!N), restore position, upper boundary (!Ub!N), dx.
      the inteegral sign used here is rahter small alternatively one can use string(byte(73)) also in the MAth and special font (!9) but it is rather large …
  • a vector and its absolutum
    • $\vec{v}$: '!Sv!R!9!U'+string(byte(54))+'!N!X'
      (save position(!S), draw letter v, go back to start position (!R), select special font (!9), upper position (!U), arrow (string(byte(54))), normal position (!N), previos font (!X) )
    • $||\vec{v}||$: '!9'+string(byte(35))+'!X'!Sv!R!9!U'+string(byte(54))+'!N'+string(byte(35))+'!X'
      where the aboslutum symbol is generated with '!9'+string(byte(35))
  • more complex constructions show the limits:
    • fraction $\frac{a}{1-x}$
      !S!A a !R!B1-x!N
      i.e.: save position (!S), draw nominator (!A), go back to saved position and start denominator (!R!B), return to normal when done (!N at end)
      as there is no automatic centering the a in the nominator is surrounded here with two spaces which is not exact. And there is no fraction bar: you have to do it on your own which means you need to know the length of the object. This could look like this (with no guarantee):
  xyouts, x,y , '!S!A a !R!B1-x!N' , width=wdt 
  plots , [x, x+wdt ] , [ y+dy , y+dy ]%%
  • The position of the fraction line must be corrected a bit upward therefore the dy8). You may try to calculate dy but at a certain point it becomes incredible complex …

For more examples have a look at Formatting Command Examples.

graphics -> screen

With a call to plot (and similar functions) a grafic window is opened (except you have changed the output device, see below). This x-terminal (linux/linux) does not perform a refresh when covered by another window and it uses only 256 colors.

For an automatic refresh set the retain flag to two:

SET_PLOT,'X'
DEVICE, RETAIN=2

This works only if the x-window is opened for the first time. If a x-terminal is already open close it before you call the device.

If you want “true colors” (i.e. 24bit colors) use:

SET_PLOT,'X'
DEVICE, /DECOMPOSED

Colors are then defined by hexadecimal numbers. Red, green, blue and combinations can be defined by:

red   = '0000ff'x
green = '00ff00'x
blue  = 'ff0000'x
yellow= '00ffff'x
magenta='ffff00'x
cyan  = 'ff00ff'x
black = '000000'x
white = 'ffffff'x
grey  = '888888'x

Farbe, colortables

Farbe wird in idl üblicherweise als Index in eine Farbpallete (=colortable) interpretiert. Diese Tabellen können maximal 256 Einträge enthalten. Einige plotdevices können aber auch true color (also 3*8Byte=24bit farbwerte).

Alle Ausgaben in das Plotdevice können farbig erfolgen - dies geschieht mit der color = <wert> option, wobei <wert> entweder ein index in eine Farbpalette ist oder ein true colorwert.

colortables

From version 8 on IDL takes truecolor rgb values which you give as 3byte integer values. You can write them as Hex-numbers like color='0000ff'x, color='00ff00'x and color='ff0000'x for red, green and blue, respectively.

Before version 8 IDL used colortables, the color appearing on the screen was defined in a table or palette, i.e. a list with 256 entires for the r,g,b truecolor values. There are several predefined colortables (currently 73). You can load them with

LOADCT, ct_index

where ct_index is the index of the color table. A command PLOT,x,y,color=4 plots data with the color defined in the palette for index 4. The default palette ist a grayscale grayscale with 256 entries.

To print a list of the color table names use

  loadct, get_names=s
  for i = 0, n_elements(s)-1 do print, i, ':', s[i]

Current list is

     0:B-W LINEAR
     1:BLUE/WHITE
     2:GRN-RED-BLU-WHT
     3:RED TEMPERATURE
     4:BLUE/GREEN/RED/YELLOW
     5:STD GAMMA-II
     6:PRISM
     7:RED-PURPLE
     8:GREEN/WHITE LINEAR
     9:GRN/WHT EXPONENTIAL
    10:GREEN-PINK
    11:BLUE-RED
    12:16 LEVEL
    13:RAINBOW
    14:STEPS
    15:STERN SPECIAL
    16:Haze
    17:Blue - Pastel - Red
    18:Pastels
    19:Hue Sat Lightness 1
    20:Hue Sat Lightness 2
    21:Hue Sat Value 1
    22:Hue Sat Value 2
    23:Purple-Red + Stripes
    24:Beach
    25:Mac Style
    26:Eos A
    27:Eos B
    28:Hardcandy
    29:Nature
    30:Ocean
    31:Peppermint
    32:Plasma
    33:Blue-Red
    34:Rainbow
    35:Blue Waves
    36:Volcano
    37:Waves
    38:Rainbow18
    39:Rainbow + white
    40:Rainbow + black
    41:CB-Accent
    42:CB-Dark2
    43:CB-Paired
    44:CB-Pastel1
    45:CB-Pastel2
    46:CB-Set1
    47:CB-Set2
    48:CB-Set3
    49:CB-Blues
    50:CB-BuGn
    51:CB-BuPu
    52:CB-GnBu
    53:CB-Greens
    54:CB-Greys
    55:CB-Oranges
    56:CB-OrRd
    57:CB-PuBu
    58:CB-PuBuGn
    59:CB-PuRd
    60:CB-Purples
    61:CB-RdPu
    62:CB-Reds
    63:CB-YlGn
    64:CB-YlGnBu
    65:CB-YlOrBr
    66:CB-BrBG
    67:CB-PiYG
    68:CB-PRGn
    69:CB-PuOr
    70:CB-RdBu
    71:CB-RdGy
    72:CB-RdYlBu
    73:CB-RdYlGn
    74:CB-Spectral

Die aktuell geladene Farbpalette kann mit

TVLCT, rgb , /Get

in das array rgb geladen werden, oder mit

TVLCT, red, grn, blu , /Get

in die arrays red, grn, und blu.

Der folgende Beispielcode liest die aktuelle Farbpalette und definiert ein paar Farben um und macht diese neue Palette zur aktuellen:

; get actual colortable
TVLCT, rgb , /Get
; size of this colortable
n_col = n_elements( rgb[*,0] )
; define some indices into the table
col_black = 0
col_red = 1
col_green = 2
col_blue = 3
col_yellow = 4
col_gray = 5
col_white = n_col-1
; define the rgb components of the colors related to this indices
rgb[ col_red   , * ] = [   0 ,   0 , 255 ]
rgb[ col_green , * ] = [   0 , 136 ,   0 ] 
rgb[ col_blue  , * ] = [ 255 ,   0 ,   0 ]
rgb[ col_yellow, * ] = [   0 , 255 , 255 ]
rgb[ col_gray  , * ] = [ 136 , 136 , 136 ]
; IDL uses the first and the last entry of the colortable 
; as background and primary drawing color i.e. as black an white 
; redefine it here ...
rgb[ col_black , * ] = [   0 ,   0 ,   0 ] 
rgb[ col_white , * ] = [ 255 , 255 , 255 ]
; make this new colortable active
TVLCT, rgb
...
; plot red axis with red line
plot, x,y , color=col_red
...

Ähnlich wirkt der IDL befehl TEK_COLOR der allerdings keine Namen definiert. First nine colors are: 0=black, 1=white, 2=red, 3=green, 4=blue, 5=cyan, 6=magenta, 8=orange.

Klassiche Farbpalleten die man auf diese Weise definieren könnte sind z.B. die CGA-Palette, die EGA-palette oder die VGA-pallette.

Einen Farbverlauf kann man ähnlich definieren. Der folgende code definiert ein Spektrum in dem, orientiert an vereinfachten Empfindlichkeitskurven der Farbrezeptoren des Auges die Kurven für rot, grün und blau gesetzt werden:

; calc. a spectrum starting with red going via yellow and green to blue and violet
; get current color table
TVLCT, RED, GRN, BLU , /Get
; size of this colortable
n_colors = n_elements( RED )
; maximum value of one channel
max_rgb = 255
; the very first and last points (col=0 and col=N-1) are excluded because idl uses them as background and foreground colors
for col = 1 , N_colors-2 do begin
  RED(col) = fix((cos(2.*!pi*0.8*(float(col-1)/(N_colors-3)    ))+1.0)/2.0*max_rgb)
  GRN(col) = fix((cos(2.*!pi*    (float(col-1)/(N_colors-3)-0.3))+1.0)/2.0*max_rgb)
  BLU(col) = fix((cos(2.*!pi*0.5*(float(col-1)/(N_colors-3)-1.0))+1.0)/2.0*max_rgb)
  endfor
; load this color table
TVLCT, RED, GRN, BLU

true color, direct color

In the 'true color' or 'direct color' system are the 3 lowest bytes of an integer number interpreted as the intensity values for red, green and blue. If you write this as a hex number you may define rather easily arbitrary colors. Here are some basic colors as true colors (compare also with 'html colors' eg in http://www.w3schools.com/html/html_colors.asp):

col_black  = '000000'x
col_red    = '0000ff'x
col_green  = '008800'x
col_blue   = 'ff0000'x
col_yellow = '00ffff'x
col_gray   = '888888'x
col_white  = 'ffffff'x

To use true colors the plotdevice must be configured accordingly. This is done with the /decomposed keyword, e.g. for UNIX X-windows device:

SET_PLOT, 'X'
DEVICE, /decomposed

One may also define colors directly as 24bit numbers from red, green and blue values. Assume that red, grn and blu are byte values (8-bit numbers) in the range 0..255, the respective '24bit true color' values is then:

true_color_rgb = long(red) + ishft(long(grn),8) + ishft(long(blu),16)

or if you have your red, grn blu values as floats in the range 0..1

true_color_rgb = long(red*255) + ishft(long(grn*255),8) + ishft(long(blu*255),16)

Plot system variables

!P is a structure containing system parameters describing the plot

!P.MULTI → multiplot

  • !P.MULTI[0] number of plots remaining on the page
  • !P.MULTI[1] plot columns per page.
  • !P.MULTI[2] rows of plots per page.
  • !P.MULTI[3] plots stacked in the Z dimension.
  • !P.MULTI[4] is 0 to make plots from left to right (column major), and top to bottom,
    1 to make plots from top to bottom, left to right (row major).

!P.charsize → relative size of all characters, default: 1=normal size

  • 2 would double the size of all text in the plot
  • 0.75 would reduce the size of all characters by about 25%

!D is a structure containing parameters about the current plot-Device
!D.x_size and !D.y_size give the width of the device in device coordinates
!D.x_ch_size and !D.y_ch_size give the base size of characters in device coordinates
⇒ !D.y_ch_size / !D.y_size gives the base size of characters in normalized coordinates

!X, !Y and !Z are structures containing parameters determining the appearance of the X,Y, and Z axes
!X.Margin = [left, right] give the width of the left and right margin between the axes and the plot region in characterwidths
!Y.Margin = [bottom, top] give the width of the left and right margin between the axes and the plot region in characterheights

Some variables contain information about the last plot

!X.S and !Y.S give the scaling factor for conversion from data to normalized coordinates. It is

x_norm = !X.S[0] + !X.S[1] * x_data
x_norm = !X.S[0] + !X.S[1] * log_10(x_data)

for linear and logarithmic scaling respectivley

!X.crange = fltarr(2) contains the range used by the x-axis.

!X.window = fltarr(2) contains the normalized x-coordinates of start and endpoint of the axis.

!X.region = fltarr(2) contains the normalized x-coordinates of the plotting region

Every single plot goes into a region which should contain the plot itself, the axis labels, the axis titles and the plot title. !X.Margin determines the distance of the x-axis to the bottom margin of this plot region.

If you want to know where ticks are made then use keywords xtick_get and ytick_get when calling plot:

plot, x, y, xtick_get=xticks, ytick_get=yticks 

See also the Graphics System variables in the IDL help. A (horrible) description of the different coordinate systems of IDL can be found under Direct Graphics Coordinate Systems .

graphics function

For the pixel based Windows, X and Z Device it is possible to define a bit-operation for every pixel drawn. I.e the color bits for every pixel of a line are first connected with the background of its position. This can be used to make lines contrasting with the underlying background. The operation can be defined with the SET_GRAPHICS_FUNCTION parameter of the DEVICE function :

DEVICE, GET_GRAPHICS_FUNCTION = old_gfct, SET_GRAPHICS_FUNCTION = 6

with graphics_function = 6 = GXxor an XOR operation between pixel color and the content of the pixel. The GET_GRAPHICS_FUNCTION variable stores the current state. The default is GXcopy = 3 - i.e A list of the possible grafics functions can be found under idl help → Keywords Accepted by the IDL Devices -> SET_GRAPHICS_FUNCTION

The result of this pixel operations can give weird results …

transparency

IDL does not know transparent overlays but on pixel based devices you can implement it with TVRD and TVRD. Lets assume you want to put a bitmap BMP with size Nx, Ny at a position x0 y0 (pixels) with a tranparency factor of q_tra:

; get current content of canvas as a bytarr( N_channel, Nx, Nx )
bgnd = TVRD( x0,y0, Nx, Ny, true=1 )
; blurr it a bit
N_blurr = 8
bgnd = SMOOTH( bgnd, [ 1, N_blurr, N_blurr ] , /edge_truncate )
; create a new bitmap by combining bmp and bgnd 
bmp = byte( q_tra * float(bmp)  +  (1-q_tra) * float(bgnd) )
; put the new bmp into the canvas
TV, bmp, x0, y0, true=1, /device

This is implemented in mk_legend.pro

coordinate transform

in classical 2D plots IDL knows three coordinate systems. In plot commands as plot, oplot, xyouts, plots you can refer to them via key words

  • /data the coordinate system defined by the current axes
  • /normal normalized coordinates within the axes running from zero to one
  • /device device coordinates in pixel or postscript points

to convert between them idl uses several parameters (see Direct Graphics Coordinate Systems ):

  • /data to /normal is determined via !X.S :
    x_norm = !X.S[0] + !X.S[1]*x_data for !X.type=0 (linear) and
    x_norm = !X.S[0] + !X.S[1]*log(x_data) for !X.type=1 (logarithmic)
    and the respective forms for y (→ !Y.S) and z (→ !Z.S)
  • /normal to device is determined via !D.X_VSIZE and !D.Y_VSIZE:
    x_device = x_norm*!D.X_VSIZE

Postscript

If you want scalable high quality plots with great fonts use encapsulated postscript (eps). Postscript is a vectorgrafics format and encapsulated means that the grafic can be easily included in other documents (if your software can deal with eps :-)).

But be aware that if your plot is made up by many plot commands the result will be an incredible large file. Nevertheless you can convert eps to pdf (which is still a vector grafics format but has an internal compression, see below) or to png (see below). If an eps files is very large the pdf will be large (about 1/4) and a png will be 'normal' (what you may used to). Never use jpeg for line plots or everyting which is not a photograph.

to produce an encapsulated postscript plot:

SET_PLOT,'ps' ; select postscript device
!P.font = 0 ; select postscript fonts
DEVICE, /ENCAPSULATED, /portrait, /HELVETICA, /COLOR ; basic setup for colored EPS 
DEVICE, /COLOR, /decomposed ; in idl 8 is true color = /decompose possible 
; DEVICE, FILENAME="idl_plot.eps" ; put a filename here if you do not ike the default from idl ('idl.ps') but note that this name is reset with every DEVICE,/CLOSE call
; DEVICE, XSIZE=17.8, YSIZE=12.7 ; size in centimeter ! these are the default values corresponding to a 640*480 pixel image printed at 96pixel per inch...
DEVICE, XSIZE=29.4, YSIZE=21.0 ; size in centimeter ! DIN A4
DEVICE, /SYMBOL ,  FONT_INDEX=4 ; make greek letters available as font #4

; device keyword output is intended to add own postscript code,
; BUT it generates also ps-code for a new page.
; See also comment in http://www.physics.wisc.edu/~craigm/idl/down/pxperfect.pro
; DEVICE, OUTPUT = '%%Title: plot by Jan Schween'

; in post script we need thicker axis lines
!X.thick = 3
!Y.thick = 3

; plot in black on white background
!P.color = '000000'x
!P.background = 'ffffff'x

; for a good readable plot charsize should be larger
!P.charsize = 2.

...
plot commands
...

DEVICE,/CLOSE

; if we did not provide a plotname with DEVICE, plotname+'.eps' we can rename it here...
SPAWN, 'mv idl.ps '+plotname+'.eps' ; rename outputfile
  • mit /portrait wird das koordinatensystem nicht gedreht und die bbox stimmt
  • mit /landscape wird das koordinatensystem gedreht und die bbox stimmt nicht mehr

in idl 6.4 ist unter postscript farbe nur aus einer farpalette mit 256 enträgen möglich. Mit:

SET_PLOT,'ps'
DEVICE, ... /COLOR, BITS_PER_PIXEL=8 ...

ist es möglich 2 dimensionale arrays intarr(Nx,Ny) als farbige bitmaps in PS darzustellen. Die Farbe ergibt sich dabei aus der aktuellen colortable - die hat maximal 256 einträge also einen 8Bit langen indexraum.

Im Postscript device kann man zwar true color jpegs darstellen:

READ_JPEG, filename, img
TV, img, TRUE=1

Allerdings wird das jpg bild anscheinend auf eine 8Bit = 256Farb-Palette reduziert…

In idl 8.2 kann man mit true color arbeiten:

SET_PLOT,'ps'
DEVICE, /COLOR, /decomposed, ...

ermöglicht die verwendung von true color - z.B.:

plot, x,y, color='00ffff'x 

benutzt Gelb (=max red + max green) als Farbe.

Device coordinates in the postscript device are in 1/1000cm (1000pixel per cm). To make a plus sign at 1cm,1.5cm use

plots, 1*1000,1.5*1000, psym=1, /device

Postscript - non encapsulated :

SET_PLOT,'ps'
DEVICE [  ,/portrait | ,/landscape ]
...
plot commands
...
DEVICE,/CLOSE

(→ …/idl/eps_test/ps_landscape.pro)

  • IDL produces per default a 'seascape' plot erzeugt (in gv muss man seascape oder landscape+swap landscape anwählen)
  • /landscape thus has no meaning !
  • /portrait turns the coo-system by 90° to the left …
  • in contrast to /ENCAPSULATED the bbox is ok then !
  • for details of the postscript device see here

Basic commands, select device driver

SET_PLOT, Device

e.q. for Postscript

SET_PLOT,'ps'

or for colored EPS

SET_PLOT,'ps',/ENCAPSULATED,/COLOR

with only the SET_PLOT command output will be send to the file 'idl.ps'
this can be changed by

DEVICE, FILENAME='<filename>', ...

the plot-file is then finalized with

DEVICE, /CLOSE

or

DEVICE, /CLOSE_FILE

Difference between postscript (*.ps) and encapuslated postscript (*.eps)

  • postscript is a vector grafics format the predecessor of pdf.

That means grafs can be enlarged or shrinked without loosing quality.

  • there is no big difference between PS and EPS:
    • EPS means that the graph can be imported into other applications.
    • That means only one single page is allowed - no multiple pages
    • the graf must have an information about its size - the bounding box (bbox)

postscript -> png

Convert postscript to png with imagemagick:

convert -density 120 <ps-filename> <filename>.png

density 120 means 120 dpi, increasing this values increases quality and size of the png. You must have ghostscript installed. For more control you may want to directly use the ghostscript call for this:

gs -dSAFER -dBATCH -dNOPAUSE -sDEVICE=png16m -r120 -dTextAlphaBits=4 -dGraphicsAlphaBits=1 -dEPSCrop -sOutputFile=<filename>.png <ps-filename>

Parameters -dSAFER -dBATCH -dNOPAUSE makes gs to run this as batch job with no questions to the user,
-sDEVICE=png16m defines true color rbg png as output format,
you may use png256 or png16 for 256 or 16 element palette images which give substantilly smaller images but you may experience ditherung artefacts (for details on the gs devices see gostscrip webpages).
-r120 means 120dpi and gives a 1388×992 pixel images for a DINA4 landscape postscript,
-dTextAlphaBits=4 ensures smooth characters after rastering,
-dGraphicsAlphaBits=1 avoids gridlines after rastering (http://www.idlcoyote.com/ps_tips/psstripes.html),
-dEPSCrop crops the image at the bounding box.

postscript -> PDF

One can convert eps files into pdf using the program ps2pdf of the ghostscript package. The following command creates a pdf file quelle.pdf from the file quelle.eps where the 'papersize' is adapted to the BoundingBox of the eps file.

ps2pdf -dEPSCrop quelle.eps

Since ps2pdf is based on ghostscript all gs options can be used.

To fit the plot to a standard paper size (i.e. Letter or DINA4 depending on your system settings) use

ps2pdf -dEPSFitPage quelle.eps

One can also invoke ghost script (gs) to control more precisely what you want.

To create a PDF with a rgb colors use :

gs -dBATCH -dNOPAUSE -sColorConversionStrategy=RGB -sDEVICE=pdfwrite -dEPSCrop -sOutputFile=quelle.eps.pdf  quelle.eps

To create a PDF based on PDF-A standard (the definitions file PDFA_def.ps should be in the gs/lib dirictory … no guarantee that this works …)

gs -dPDFA=1 -dBATCH -dNOPAUSE -sColorConversionStrategy=RGB -sDEVICE=pdfwrite -sOutputFile=quelle_a.pdf  PDFA_def.ps  quelle.eps

SVG

Scalable Vector graphics can be displayed by web browsers and might get the standard vector graphic format for web applications

There are converters from eps to SVG:

pdf2svg

is a simple converter from pdf to svg. Simple because it converts fonts to vector grafics… The conversion is done in two steps:
1: convert eps to pdf

ps2pdf -dEPSCrop wind_rose_all.eps

2: convert pdf to svg

pdf2svg wind_rose_all.pdf wind_rose_all.svg

but fonts look really ugly !

pstoedit

is a package to convert postscript to something. To convert to svg use:

pstoedit -f plot-svg -psarg "-dEPSCrop" <file.eps>  <file.svg>

unfortunately the -dEPScrop option does not work properly. The graph sits in the lower corner and is partially cropped.

JPG, PNG and GIF

Raster-Oriented (or Bitmap) graphics can be created with:

  ; set the z-device => plot commands go into the Z-Buffer - this is just a bitmap
  SET_PLOT,'Z'
  ; configure this bitmap: true color (24bit=3byte) and size 640*480 (default)
  DEVICE, SET_PIXEL_DEPTH=24, /DECOMPOSED, set_resolution=[ 640 , 480 ]
  ...
  plot commands 
  ...
  ; read actual graf into array img as BYTARR( N_color , width , height )
  img = TVRD( true=1 )
  ; write into png image
  WRITE_PNG, 'idl.png', img
  ; or write into jpg image
  WRITE_JPEG, 'idl.jpg', img

This creates true color graphic files.

If a 256 color palette shall be used - which reduces the size of the grafics file and which is the default for idl and for gif but also possible with png the code must be changed:

  ; configure the bitmap: 256 color palette (8bit=1byte) and size 640*480 (default)
  DEVICE, SET_PIXEL_DEPTH=8, set_resolution=[ 640 , 480 ]
  ...
  ; read actual graf into array img as BYTARR( N_color , width , height )
  img = TVRD( )
  ; read actual palette
  TVLCT, red, grn, blu , /Get
  ; write into png image
  WRITE_PNG, 'idl.png', img, red, grn, blue

It is also possible to copy an image from the X-Device (i.e. the screen) but this might be disturbed by redraw commands or overlap from other windows (see /home/jschween/exp/ideen/idl/mk_png/…) ⇒ Use the Z-Buffer instead.

animated GIF

It is possible to create animated gifs by repeatedly writing to the same gif file with the /MULTIPLE flag set. GIF knows only palette based color with in maximum 256 colors. We thus either need to work in a 8bit color device, or we quantizie the image colors before providng it to the WRITE_GIF :

From a 8 bit color device :

  ; plot into virtual device = bitmap
  SET_PLOT, 'Z'
  ; configure this bitmap: 8 bit colors and size is 640*480 (default)
  DEVICE, SET_PIXEL_DEPTH=8, set_resolution=[ 640 , 480 ]
 
  ; get current color palette
  TVLCT, rgb , /get
 
  ; go through images
  FOR i_img = 0, N_img-1 DO BEGIN
 
    ; create plot 
    ...
 
    ; read actual graf into array img
    bmp = TVRD( )
    ; append img to gif-file
    WRITE_GIF, 'idl.gif', bmp,  rgb[*,0],rgb[*,1],rgb[*,2],  /MULTIPLE, delay_time=6, repeat_count=0
 
  ENDFOR ; i_img
 
  ; close gif
  WRITE_GIF, 'idl.gif', img, /CLOSE

From a true color device

  ; plot into virtual device
  SET_PLOT, 'Z'
  ; configure this bitmap: 24 bit colors and size is 640*480 (default)
  DEVICE, SET_PIXEL_DEPTH=24, set_resolution=[ 640 , 480 ]
 
  ; go through images
  FOR i_img = 0, N_img-1 DO BEGIN
 
    ; create plot 
    ...
 
    ; get current bitmap
    bmp = TVRD( true=1 )
    ; quantize colors - derive gif color translation table from first image
    if i_img eq 0 then gif = COLOR_QUAN( bmp, 1, red, grn, blu, get_translation = gif_col_trans ) $
                  else gif = COLOR_QUAN( bmp, 1, red, grn, blu,     translation = gif_col_trans )
    ; append img to gif-file
    WRITE_GIF, 'idl.gif', gif,  red, grn, blu,  /MULTIPLE,  delay_time=6, repeat_count=0
 
  ENDFOR ; i_img
 
  ; close gif
  WRITE_GIF, 'idl.gif', /CLOSE
  • The /MULTIPLE keyword in the call of WRITE_GIF creates at the first call a GIF-file but does not close it. During subsequent calls the images are appended. Accordingly the GIF-file must be closed by a final call with the /CLOSE keyword.
  • The delay_time is given in 1/100sec; delay_time=6/100sec here corresponds to a frame rate of ~17images/sec which is about the frame rate of the old film standard super8. Television has usually 25frames/sec corresponding to a delay of 4/100sec.
  • The parameter repeat_count=0 makes the animation to be repeated forever.
  • Graphics commands must go into a bitmap device in this example the Z-device (it might be possible to do this also in the X-device (UNIX/LINUX), or the win-device (windows). Be aware that the color setup is different than for printer devices (like e.g. postscript): background is black, foreground color (axes of a plot etc.) are white.

reading images

image files can be read into arrays, displayed, analyzed and manipulated

  ; read image
  img = read_png( 'img.png' )
  
  ; print out dimensions
  help, img
  
  ; determine dimensions
  img_size = size(img)
  ; number of color channels, columns and rows
  Nc = img_size[1]
  Nx = img_size[2]
  Ny = img_size[3]
  
  ; alternative: query
  qr = query_png( 'img.png' , info )
  Nx = info.dimensions[0]
  Ny = info.dimensions[1]
  
  ; black out lower left corner
  img[ 0 , 0:10, 0:10 ] = 0
  img[ 1 , 0:10, 0:10 ] = 0
  img[ 2 , 0:10, 0:10 ] = 0

  ; display
  x = 10
  y = 10 
  tv, img, x, y , true=1
  
  ; mark center
  plots, Nx/2, Ny/2 , psym=1, /device

back and foreground color

The color scheme for bitmap devices as screen (SET_PLOT,'X' or SET_PLOT,'WIN') or the 'Z'-device (SET_PLOT,'Z') is different than for printer devices like e.g. postscript:

  • In bitmaps background is black an drawing is done in white
  • for printers background is white an drawing in black

This can be changed with:

  SET_PLOT, 'Z'
  ; set background color to white (last color in pallete)
  !P.BACKGROUND = 255
  ; set foreground color to black (first color in pallete)
  !P.COLOR = 0

If you are working with 'true colors'

  SET_PLOT, 'Z'
  ; set background color to white
  !P.BACKGROUND = 'ffffff'x
  ; set foreground color to black
  !P.COLOR = '000000'x

General remarks about graphic formats

  • JPG is good for images with continous color gradients like photographs:
    • (you remember: jp[e]g stands for joint photographic experts group).
    • Sharp edges (e.g. lines) in the image create echo like artefacts in jpg.
  • PNG is good for line graphics and images with sharp edges:
    • images with large areas with the same color have good compression rates
    • scaling may lead to moiree efects along edges
    • it is possible to give every pixel in the image a certain transparency
  • EPS / PS ([Encapsulated] Post Script) is good for pure line graphics
    • PS is a vector graphis format. I.E. the file contains plot commands like moveto, lineto, textout, …
    • if a plot contains a lot of drawing commands the file can become very large
    • bitmap images can be incorporated but are not compressed ⇒ files become large…
    • Graphics can be arbitrarily scaled. Scaling is mainly limited by the resolution of the output device.
  • PDF (portable document format) is an extension of PS
    • it has additional compression included
    • EPS and PS images can be converted to PDF …
    • JPG, PNG etc. can be included in PDF
  • GIF is only good for animations of line graphics - otherwise use PNG or JPG instead !
    • PNG or JPG have more/better colors (GIF has only 256 colors instead of the up to 16million 'true colors' of PNG or JPG)
    • compression is not as good as with PNG or JPG
    • a gif image may have one transparent color i.e. every pixel with this color are totally transparent
    • animation is the only advantage

polar

The plot procedure can work with polar coordinates - you need to set the /polar keyword

; define some data points
N = 100
theta = findgen(N)/N*360.
r_max = 7.4 ;-)
radius = r_max*findgen(N)/N

plot, radius, theta*!DTOR, $
  /polar, $
  xstyle = 1, $
  xrange = [ -r_max, +r_max], $
  ystyle = 1, $
  yrange = [ -r_max, +r_max], $
  /isotropic

If you do not fix range and isotropy of the plot it may look weird due to automatic scaling.

You may want to have axes through the center:

plot, radius, theta*!DTOR, $
  /polar, $
  xstyle = 1+4, $ ; exact scaling but invisible (box)axis
  xrange = [ -r_max, +r_max], $
  ystyle = 1+4, $ ; exact scaling but invisible (box)axis
  yrange = [ -r_max, +r_max], $
  /isotropic

; Draw x- and y-axis through 0,0 with ticks to the top or right, respectively.
AXIS, 0, 0, XAXIS=0
AXIS, 0, 0, YAXIS=0

You may want to have a radius and angle grid. IDL cant do this for you. You have to do it step by step:

plot, radius, theta*!DTOR, $
  /polar, $
  xstyle = 1+4, $ ; exact scaling and invisible (box)axis
  xrange = [ -r_max, +r_max], $
  ystyle = 1+4, $
  yrange = [ -r_max, +r_max], $
  /isotropic

; draw and anotate spokes
N_spk = 8
for i = 0, N_spk-1 do begin
  wi = (2*!pi/N_spk)*i
  oplot, /polar, [0,90], replicate( !pi/2-wi, 2 ), linestyle=1
  r = 90.
  xyouts, r*sin(wi), r*cos(wi), string(wi/!dtor,f='(I0)')
endfor ; i

; draw and annotate angle circles
N_w = 181
w = findgen(N_w)/(N_w-1)*2*!pi
N_r = 3
for i = 0, N_r-1 do begin
  ei = (90.*i)/N_r
  oplot, /polar, replicate( 90-ei, N_w), w, linestyle= i gt 0 ? 1 : -1
  xyouts, 0, ei, string(ei,f='(I0)')
endfor ; i

Some routines like XYOUTS and ARROW do not accept the polar keyword (or ignore it). You may convert your coordinates to cartesian as above or you can use the CV_COORD function to do this:

polar_coo = [[angle],[radius]]
xy_coo = CV_COORD( FROM_POLAR=polar_coo, /DEGREES, /TO_RECT )

contour

A contour plot with color shading is made with:

; prepare number of levels 
min_data = min( data, /nan )
max_data = max( data, /nan )
N_iso = 10
; we need one more levels value because level defines interval boundaries, i.e. color[i] goes into interval level[i] ... level[i+1]
levels = findgen(N_iso+1)/N_iso*(max_data - min_data) + min_data
; prepare true colors : e.g. a grayscale from white to black
gray = long(255*(1-findgen( N_iso )/(N_iso-1)))
iso_colors = gray or ishft( gray , 8 ) or ishft( gray, 16 ) ; colors as true color 3byte/24bit code
; do the contour plot
contour, data, x_coo, y_coo, $ ; data nad coordinates
  levels = levels, $ ; levels for the colors
  c_color = iso_colors, $ ; 24bit colors
  /cell_fill, $  ; avoid problems with mssing data (nan), etc. (you may use /fill instead but ...)
  xtitle = 'x_coo (units)', $ 
  xstyle = 1, $ ; use exact range of x_coo for axis range
  ytitle = 'y_coo (units)', $
  ystyle = 1 ; use exact range of y_coo for axis range

Routine conotur takes all the keywords like plot and some more - see help for contour and the descritption of graphics keywords.

Because levels give the lower border of the interval for the colors we need one more level than colors (levels=findgen(N_iso+1)...). For values below level[0] nothing is drawn (no color !), for values above level[N_iso], i.e. the last level the first color is recycled. To get colors for the values below the lowest level and above the largest level you can add additional colors and levels to what is defined above:

; expand coloring levels above and below range(levels)
iso_colors = [ col_below_min, iso_colors, col_above_max  ]
levels = [     extrm_lev_min, levels,     extrm_lev_max  ]

you may want to use the most extreme values your system can provide

extrm_lev_min = -(machar()).xmax
extrm_lev_max = +(machar()).xmax

See function function machar.

iso lines

contour, data, x_coo, y_coo, levels = levels

If you want iso-lines with annotations give the keyword /follow :

contour, data, x_coo, y_coo, $
  levels = levels, $
  c_annotation = string( levels , format = '(f5.1)' ), $
  /follow, $
  /overplot

Keyword c_annotations is used here toprovide a format - this is usually not necessary. You can define also different thicknesses, colors etc. with the other c_... key words of contour see help for contour.

color bar

You can use contour also to make a color bar:

A horizontal color bar at the bottom

; charsize in normalized coordinates for position 
ch = float(!D.y_ch_size) / !D.y_size
; the color bar
contour, [ [levels] , [levels] ]  , $ ; a two row array with just the levels from the contour plot above
  levels, [0,1], $ ; levels on the x-axis and 2 lines in y direction
  levels = levels, $ ; levels for the shading
  c_color = iso_colors, $ ; the colors itself
  /cell_fill, $
  xtitle = 'data', $
  xstyle = 1, $ ; scale x-axis precisely
  xticklen = 5*ch, $ ; ticks must be elongated
  ytitle = '', $ ; no title on y-axis 
  ystyle = 1+4, $ ; do not draw y-axis 
  charsize = 1.0, $ ; smaller charsize ...
  position = [ 0.7 , 2*ch, 0.95, 3*ch ], $ ; postion in lower right corner below axis ...
  /normal, $ ; positon in normalized coordinates
  /noerase ; do not erase beckground - plot over the contour plot

A vertical color bar at the right side:

; charsize in normalized coordinates for position 
ch = float(!D.y_ch_size) / !D.y_size
contour, transpose( [ [levels] , [levels] ] ) , $ ; a two column array with the levels from the contour plot
  [0,1], levels, $ ; two columns in x-direction and levels in y-direction
  levels = levels, $ ; levles for the shading
  c_color = iso_colors, $
  /cell_fill, $
  ytitle = '', $ ; title will be in second y-axis on right side
  ystyle = 1, $
  yticklen = -5*ch, $ ; longer ticks on this axis
  ytickname = replicate(' ', 10), $ ; no annotations on this side 
  xstyle = 1+4, $ ; do not draw x-axis
  charsize = 1.0, $ ; smaller charsize ...
  position = [ 1-4*ch , 7*ch, 1-3*ch, 1-4*ch ], $ ; position at right side ...
  /normal, $ ; position in normalized coordinates
  /noerase ; do not erase background - plot over the contour plot

; y-axis on the right side 
axis, yaxis=1, $
  ytitle='data', $
  ystyle = 1, $
  yticklen = -5*ch, $
  yrange=[ min(levels) , max(levels) ], $
  charsize = 1.0, $ ; smaller charsize ...
  /save
  

The position of the vertical color bar in this example is not perfect. You may have to define the position of the contour plot itself to provide space for the color bar.

TRIANGULATE

In case of data defined on a irregular grid you may need a triangulation, i.e. a kind of ordering of the data in triangles. This triangulation allows you to do CONTOUR plot and further interpolation of this data to a regular grid with TRIGRID.

lets Assume you have data Z at N points defined by coordinates X_coo and Y_coo. To get the triangulation just use:

TRIANGULATE, X_coo, Y_coo, triangles_idx, boundary_idx

triangles_idx is then of type lonarr(3,N_tri) containing the indices to the coordinates of the points defining the N_tri triangles.
boundary_idx is the list of indices of the coordinates of points of the convex boundary.
If the boundary of the region covered by your points has concave stretches TRIANGULATE produces triangles which cover them.
I found no parameter or function to avoid this or remove them.
If your coordinates are given in different, non isotropic units you should make them isotropic (e.g. X_coo as geographic coordinates like degrees east, and y_coo in meter above see level → use TRIANGULATE, X_coo*deg_2_meter, Y_coo, triangles_idx )

The output can be fed into CONTOUR with

CONTOUR, Z, X_coo, Y_coo, triangulation=triangles_idx, ...

TRIGRID

You can interpolate triangulated data to a regular grid with TRIGRID:

If you just want a grid with Nx x Ny points

Z_grid = TRIGRID(  x_coo, y_coo, Z, triangles_idx, Nx=Nx, Ny=Ny, xgrid = x_grid, ygrid = y_grid, missing=!Values.F_NAN )

Z_grid is then fltarr(Nx,Ny) and y_grid and y_grid are of type fltarr(Nx) and fltarr(Ny).

If you want a section defined by its limits and a step size

grid_spacing = [ dx, dy ] 
grid_limits = [ x_min, y_min, x_max, y_max ]
Z_grid = TRIGRID(  x_coo, y_coo, Z, triangles_idx, grid_spacing, grid_limits, xgrid = x_grid, ygrid = y_grid, missing=!Values.F_NAN )

You can also specify coordinates x_nodes and y_nodes to which to interpolate:

Z_grid = TRIGRID(  x_coo, y_coo, Z, XOUT = X_nodes, YOUT = Y_nodes, missing=!Values.F_NAN )

tiles

Sometimes it might be preferable to have just colored tiles around the nodes of your data. Here is an example how to do it: The coordinate system must be defined - you may use the contour command as above with the /nodata keyword

; field sizes
Nx = n_elements(x_coo)
Ny = n_elements(y_coo)
; predefine neighbour nodes
x2 = x_coo[0]
x1 = x2
; intermediate border
x12 = (x1+x2)/2.
for ix = 0, Nx-1 do begin
  ; shift through nodes and borders ...
  x0 = x1
  x1 = x2
  x01 = x12
  ; new node and border
  x2 = x_coo[min([ix+1,Nx-1])]
  x12 = (x1+x2)/2.
  ; predefine ...
  y2 = y_coo[0]
  y1 = y2
  y12 = (y1+y2)/2.
  for iy = 0, Ny-1 do begin
    ; shift through ...
    y0 = y1
    y1 = y2
    y01 = y12
    ; new node ...
    y2 = y_coo[min([iy+1,Ny-1])]
    y12 = (y1+y2)/2.
    ; value
    zij = data[ix,iy]  
    ; color index
    if zij le min_data then i_col = 0 else $
    if zij le max_data then i_col = fix(float(zij-min_data)/(max_data-min_data)*(N_iso-1)) $
                       else i_col = N_iso-1
    ; color from color index
    color = iso_colors[i_col]
    ; fill the rectangle
    polyfill, [ x01, x01, x12, x12 ] , [ y01, y12, y12, y01 ], color = color
  endfor ; iy
endfor ; ix 

arrows

IDL can draw arrows (or vectors). The basic command is arrow which takes the starting point and end point of a (or several) vectors. It can take coordinates for the data-, normalized- or device system.

arrow, x0,y0, x1,y1, [, color=...][, thick=...][ ,/data | /normal | /device ][, ...]

Allthough it accepts the /polar keyword it is ignored. If you have vectors in polar coordinates you will need to convert them with cv_coord to cartesian coordinates (=rectangular).
If you have a non isotropic coordinate system with different scaling and lengths of the x-axis and y-axis you need to correct for this:

  • we want to have in x and y the same device coordinates for an amplitude a in u or v-direction
  • to achieve this we mutliply the v component of the vector with a factor fac:
  • ⇒ dx_dev = a * !X.S[1]*!D.x_size = a * fac * !Y.S[1]*!D.y_size = dy_dev
  • if we want to have dx_dev = dy_dev we need
  • fac = !X.S[1]*!D.x_size / (!Y.S[1]*!D.y_size)

Within factor the ratio !X.S[1]/!Y.S[1] corrects for the different scaling of the axes and the ratio !D.x_size/!D.y_size corrects for the different lengths of the axes on the paper.

Example for N vectors (u[i],v[i]) at N positions x[i], y[i]

factor = !X.S[1] / !Y.S[1] * !D.x_size / !D.y_size
arrow, x, y, x+u, y+v*factor, /data

Similar with function velovect:

plot, x,y, /nodata
factor = !X.S[1] / !Y.S[1] * !D.x_size / !D.y_size
velovect, u, v*fac, x, y, /overplot

Note that you first have to define the coordinate system with e.g. the plot command such that !X and !Y are correctly defined.

histogram

IDL knows one and two dimensional histogram.

1-D histogram

A one dimensional histogram is e.g. generated with

histo = histogram( data , binsize=bin, locations = classes , /nan )

the classes variable will contain the lower borders of the bins with the first bin starting at min(data).
(histogramm is made via histo=lonarr((max-min)/binsize+1)& for i do ii=(data[i]-min)/binsize)& data[ii] += 1& ...

A quick and dirty plot can be generated with psym=10:

plot, classes, histo, psym=10

but as classes contains the lower borders of bins it is shifted a half binsize to the left and the plot begins and ends with an open box. Slightly better is to shift the classes and add zero values at begin and end.

N_classes = n_elements(classes)
classes_x = [ classes[0]-bin , classes, classes[N_classes-1]+bin ] + bin/2
histo_x = [ 0 , histo, 0 ]
plot, classes_x , histo_x , psym=10, $
  xtitle = 'class (unit)', $
  ytitle = 'freq.'

or you write an own plot-histo procedure … or you ask jan :-)

2-D histogram

Assume you have two parallel data series data_1 and data_2 and want a joint histogram :

; set bin-size, min and max and vector with bin-borders for data set 1
bin_size_1 = 0.1 ; give an adequate value
min_data_1 = min(data_1, /nan )
max_data_1 = max(data_1, /nan )
N_bins_1 = fix((max_data_1-min_data_1)/bin_size_1)+1
bins_1 = min_data_1 + (findgen(N_bins_1) + 0.5) * bin_size_1 ; output of histo refers to lower border of bins => add 1/2binsize to center them in bin
 
; set bin-size, min and max and vector with bin-borders for data set 2
bin_size_2 = 100. ; give an adequate value
min_data_2 = min(data_2, /nan )
max_data_2 = max(data_2, /nan )
N_bins_2 = fix((max_data_2-min_data_2)/bin_size_2)+1
bins_2 = min_data_2 + (findgen(N_bins_2) + 0.5)*bin_size_2
 
; make 2d Histogramm
histo_2d = hist_2d( $
  data_1[*] , data_2[*] , $ ; using [*] allows you to put together multidimensional data as eg. images ...
  bin1 = bin_size_1, min1 = min_data_1, max1 = max_data_1, $ ; if there are nans in data idl complains bin1 is not allowed => provide min and max to circumvent
  bin2 = bin_size_2, min2 = min_data_2, max2 = max_data_2 $
  )
 
; plot it
contour, histo_2d, bins_1, bins_2, $
  Nlevels = 20, $ ; some value
  /follow, $ ; annotate iso lines
  ; /cell_fill, $ ; or use color shading
  xtitle = 'data 1', $
  xstyle = 1, $
  ytitle = 'data 2', $
  ystyle = 1

2-D histogram vs time of day

Assume you have a time series and want a 2-D-histogram with values versus hour of day

; get hour from time (given as julian date)
caldat, time, month, day, year, hour, minute, second
hour_float = hour + (minute+second*60.)*60.
; alternatively
; hour_float = ((time-0.5-long(time-0.5)))*24.0

; get data 
data = fltarr( N_time )
...

; define bin sizes
bin_data = 100.0
bin_hour = 1.0
 
; construct classes
data_min = min( data )
data_max = max( data )
N_data_class = floor((data_max-data_min)/bin_data)+1
class_data = data_min + findgen(N_data_class)*bin_data + bin_data/2

; make 2d Histogramm
histo_2d = hist_2d( data , hour_float , bin1 = bin_data, bin2 = bin_hour )

3-D grafics

2 dim surface

To display a 2 dim function f(x,y) as 3-dim surface as a grid mesh use Surface shade_surf:

  ; generate function data
  Nx = 100
  Ny = 100
  ; x and y = -1..1
  x = 2*findgen(Nx)/Nx-1
  y = 2*findgen(Ny)/Ny-1
  ; N*N arrays with x and y - coordinates
  xx = x  #  replicate(1,Ny) ; # is matrix product, this gives a Nx*Ny matrix with x increasing along x direction
  yy = replicate(1,Nx)  #  y ; # is matrix product, this gives a Nx*Ny matrix with y increasing along y direction
  ; 2 dim radius array
  rxy = sqrt( xx^2 + yy^2 )
  ; a nice peaked function 
  fxy = cos(rxy*2*!pi*3) * exp(-(rxy/0.3)^2)
 
  ; plot in 3D ...
  surface, fxy,x,y, xtitle = 'x', ytitle='y', ztitle='z'

IF you want to have a solid, shaded surface use shade_surf instead:

  shade_surf, fxy,x,y, xtitle = 'x', ytitle='y', ztitle='z'

3dim iso surface

To plot a 3dimensional isosurface of a 3D-field use shade_volume to get the surface and polyshade to draw it:

  ; calucalte isosurface from data = fltarr( Nx, Ny, Nz )
  shade_volume, data, iso_value, vertices, indices
 
  ; convert vertices given in index coordiantes to data coordinates
  vertices[0,*] = xcoo[ vertices[0,*] ]
  vertices[1,*] = ycoo[ vertices[1,*] ]
  vertices[2,*] = zcoo[ vertices[2,*] ]
 
  ; set up 3D transformation 
  SCALE3, XRANGE=[xmin,xmax], YRANGE=[ymin,ymax], ZRANGE=[zmin,zmax]
 
  ; draw x-y-z axes and ...
  ; ... use data from bottom of volume for basic zero plane
  contour, $
      data[ * , * , 0 ], $
      xcoo, $
      ycoo, $
      zvalue = 0.0, $ ; relative position on z-axis
      nlevels = 255, $
      /fill , $
      /cell_fill, $ ; is necessary otherwise contours are not closed or reach diagonally over the plane ...
      xtitle='x (km)', $
      ytitle='y (km)', $
      ztitle='z (km)', $
      zrange=[ zmin, zmax ], $ ; otherwise z will be rescaled to to the range of data[*,*,0]
      /t3d, $ ; use the 3dim transformation defined above
      charsize=2.5
 
  ; draw iso surface
  img = polyshade( vertices , indices , /T3D )
 
  ; save to png file
  save_png, 'iso_surf.png', img

If no device was established img will contain the image data. If the z-buffer is used (set_plot,'z') variable img will contain a dummy value and the image can be get by use of tvrd(). If you are using the z-buffer several subsequent calls to polyshade and contour (with keyword /overplot) can be made.

3 dim data

To display 3 dimensional data with semi transparent coloring use function voxel_proj.

There are several more functions see 3D Visualization in the Categorial list of functions.

principles

IDL uses the system matrix !P.T to describe the 3D-projection. It can be manipulated with the functions

  • surfr to define the viewing angles as rotation angles aroud the axes
  • scale3 to define scaling along x,y,z and z-axis and to define the viewing angles
  • t3d reset, set and manipulate the transformation matrix and its parts

There is an article Perspective projection in the english wikipedia. The basics are:

We want to map an object point in object space A=(Ax,Ay,Az) to an image point on the screen B=(Bx,By,Bz) where Bz is its position relative to the screen (behind or in front - important for hidden lines).

The observer or camera is at C=(Cx,Cy,Cz), its viewing direction is defined by the 'eulerian' angles t=(tx,ty,tz) and is position relative to the screen by E=(ex,ey,ez) (i.e. E is given in the coordinate system of the camera).

The following steps are performed:

  • D' = A-C position of A relative to the camera
  • D = T*D' rotate D into the camera system with rotation matrix T = f(tx,ty,tz) = [ [ Txx, Txy, Txz ] , [ Tyx , … ] , … ]
  • B = (D-E)*ez/dz project D onto the screen plane

Idl system matrix !P.T is a 4*4 Matrix describing the operation B' = !P.T * (A-C)

Bx'    =  (  Txx  ,  Txy  ,  Txz    ,  -ex   )  *  (  Ax-Cx  )
By'    =  (  Tyx  ,  Tyy  ,  Tyz    ,  -ey   )  *  (  Ay-Cy  )
Bz'    =  (  Tzx  ,  Tzy  ,  Tzz    ,  -ez   )  *  (  Az-Cz  )
wz'    =  (   0   ,   0   ,  -1/ez  ,   1    )  *  (    1    )

or something like that … (there is something wrong with -ez and -1/ez)

The image coordinates are calculated then by Bx = Bx'/wz'.

The rows of rotation matrix T can be interpretad as the unit vectors describing the orientation of the screen in the object coordinate system.

3D into pdf

PDF supports in principle 3D grafics - i.e. the PDF standard describes a format to incorporate 3D models into PDF. This format is supported by the ADOBE reader since version 8 or 9, by other readers usually not. Examples can be found in this idl thread of compgroups.
http://dx.doi.org/10.1111/j.1365-2966.2011.19900.x
http://dx.doi.org/10.1038/nature07609
http://dx.doi.org/10.1126/science.1220574
A tutorial how to produce such 3D grafics can be found at www.astrobetter.com. Three 3 steps are suggested:

  1. produce a 3D representation of the data in .obj code i.e. a (ascii) text representation
  2. convert the .obj files into universal 3D a XML standard for hierarchical 3D data
  3. generate a PDF file with LaTeX and PDFLaTeX and embed the .u3d data by using the movie 15 package

Time/Date

current time as string in the standard form of your system

t = SYSTIME()

current time in UTC as string …

t = SYSTIME( /UTC )

current time in Julian Day (Days (in fractions) since November 24, 4714 BC, 12:00:00 UTC)

t = SYSTIME( /JULIAN, /UTC )

Actually /julian alone gives not UTC - you have to add the flag /UTC

current time in unix epoch (seconds since 1.1.1970 00:00:00 UTC - watch out for Jan 19, 2038)

t = SYSTIME(/seconds)

this time contains fractions of seconds, can be used to find out how long program steps take:

t0 = SYSTIME(/sec)
... do something ...
t1 = SYSTIME(/sec)
print, 'this took', t1-t0,'sec', f='(A,f0.3,A)'

current time as string formatted humand readable downto milliseconds:

t = STRING( SYSTIME(/JULIAN), FORMAT='(C(CDI02,".",CMoI02,".",CYI04,"/",CHI02,":",CMI02,":",CSF06.3))')

Actually SYSTIME(/JULIAN) makes only steps of full seconds, accordingly the fractional seconds will remain zero. But we can use the (unix-)seconds output, convert it to julian and print:

sec_per_day = 24d*60d*60d
ref_day = julday(1,1,1970,0,0,0)
t = STRING( ref_day+SYSTIME(/SEC)/sec_per_day, FORMAT='(C(CDI02,".",CMoI02,".",CYI04,"/",CHI02,":",CMI02,":",CSF06.3))')

Julian day

Julian date = time in days since 1. Januar −4712 (4713 BC) 12:00 TDT ( = UTC + 1min 5.184sec )
see http://en.wikipedia.org/wiki/Julian_date or http://de.wikipedia.org/wiki/Julianisches_Datum

Julian day is a continous time variable and can easily be used to

  • calulate time differences (dt = t2-t1 in days, dt=(t2-t1)*24. in hours … )
  • print formated times (see below)
  • annotate grafics

julian day is the IDL format for time. It is stored as a double variable counting the days.

Be aware that zero fractional part refers to 12:00 (as the start time of julian day is 12:00)

create and decode time

To create a time in julian date use function julday:

time = julday( month, day, year, hour, minute, second )
  • You can omit the time (hour, minute, second).
  • If you provide at least the hour you get a double precision variable, if not, you get an integer variable of type long.
  • If one of the input variables are arrays you get an array.
  • If these arrays have different size IDL uses the smallest size and gives no(!) warning.
  • Zero input values for all except year are allowed
  • If year is zero you get an error message “there is no year zero in the civil calendar”
  • Negative input values are allowed.
  • You can provide strings as input, but be aware that empty strings are interpreted as zero.

To calculate a date use procedure caldat :

caldat, time, month, day, year, hour, minute, second
  • if time is an array output month, day, etc will also be arrays

print formatted date from julian day in two steps:

CALDAT, t, M,D,Y,HH,MM,SS
PRINT, D,'.',M,'.',Y,' ',HH,':',MM,':',SS,FORMAT="(i2,a,i2,a,i4,a,i2,a,i2,a,i2)"

or more elegant in one step for DD.MM.YYYY hh:mm:ss :

PRINT, t, FORMAT='(C(CDI02,".",CMOI02,".",CYI04," ",CHI02,":",CMI02,":",CSI02))'

or for YYMMDDhhmmss :

PRINT, t, FORMAT='(C(CYI02,CMOI02,CDI02,CHI02,CMI02,CSI02))'

you can also have fraction of a second for hh:mm:ss.ttt i.e. seconds downto 1/1000.sec use:

PRINT, t, FORMAT='(C(CHI02,":",CMI02,":",CSF06.3))'

the function STRING can be used as well; for DD.MM.YYYY hh:mm:ss use:

s = STRING( t, FORMAT='(C(CDI02,".",CMOI02,".",CYI04," ",CHI02,":",CMI02,":",CSI02))' )

for YYMMDDhhmmss use:

s = STRING( t, FORMAT='(C(CYI02,CMOI02,CDI02,CHI02,CMI02,CSI02))' )

The format specifiers are construced as:

C<field identifier><format specifier>[<leading zero>]<number of digits>
* <field identifier> can be D for day, MO for month, Y for year, H for hour ...
* <format specifier> can be F for float, I for integer, A for character
* <leading zero> give a zero for leading zeros
* <number of digits> 2 makes a field with a width of 2 digits/characters

(see Help for "format codes" -> "C() format codes" )

Rounding

The string-formatting routine does not round the fields, but instead delivers the floor of each field. This may result in e.g. <hh-1>:59 if you ought to have hh:00 but have a 1/10000s missing. To avoid this add half of the smallest unit you do print and round times:

  • for hh:mm use print, round(time*minperday+0.5)/minperday, format='(C(CHI02,":",CMI02))'
  • for hh:mm:ss use print, round(time*secperday+0.5)/secperday, format='(C(CHI02,":",CMI02,":",CSI02))'
  • for hh:mm:ss.ttt use print, time, format='(C(CHI02,":",CMI02,":",CSF6.3))' (fractions of a second are rounded ???)

with

minperday = 24d*60d = 1440.
secperday = 24d*60d*60d = 86400.

lowest resolvable unit

Julian day comes in type DOUBLE-Precision which has a mantissa of 52 bits. The smallest resolvable step is thus dt_rel=1/2^52=2.2E-16. As we have currently (2019) Julian day number t=2458695 this is dt_min=dt_rel*t*sec_per_day=4.7E-5sec=0.047msec (sec_per_day=24.*60*60=86400). You can test this with this code (:-/):

spd=86400d&t0=julday(1,1,1970,0,0,0)&twt=1e-6&for i=0,99 do begin&t1=systime(/sec)/spd+t0&wait,twt&t2=systime(/sec)/spd+t0&print,(t2-t1)*spd&end

It will most of the time print 0.0000 and sometimes 4E-5. If you lower twt (t_wait) it will always print 0.0000

convert time formats

'human' date to julian date

t = JULDAY(Month, Day, Year [,hour[,minute[,second]]] )

if you omit hour, minute, second you will get type long, otherwise double

string to julian date for a string of the form DD.MM.YYYY hh:mm:ss:

; DD.MM.YYYY hh:mm:ss
; 0123456789 12345678
;   JULDAY(   month      ,   day        ,   year       ,    hour       ,    minute     ,    second      ) 
t = JULDAY( strmid(s,3,2), strmid(s,0,2), strmid(s,6,4), strmid(s,11,2), strmid(s,14,2), strmid(s,17,2) )

Time differences can be easily calculated - but be aware that julian date starts at 12:00 - if you want display time differences as time strings you must subtract a half day:

PRINT, t1-t2 - 0.5 , FORMAT='(C(CHI02,":",CMI02,":",CSI02))'

To annotate a plot axis with formatted time strings just set an appropriate format specifier in the plot command:

plot, time, data, ..., xtickformat = '(C(CHI02,":",CMI02))', ...

where time is given in julian day …

Unix time (seconds since ...)

Unix time are the seconds since 1.1.1970 00:00:00 UTC
Sometimes it is also called Epoch (see http://en.wikipedia.org/wiki/Unix_time)

Originally t_unx is defined as long integer - this leads to the year 2038 Problem

Julian day (t_jul) to unix time (t_unx):

secperday = 24*60*60d
unx_start = JULDAY( 1, 1, 1970, 0, 0, 0 ) ; this is equal to 2440587.5
t_unx = (t_jul - unx_start) * secperday

and vice versa:

t_jul = t_unx/secperday + unx_start

Other time formats

If you have a time t_xxx as tics since t_ref (e.g. (fractional)number of 7min intervals since 3.4.1722 13:21 (just to show that it can be really weird :-) ) then define:

tics_per_day = min_per_tic / min_per_day  or  sec_per_tic/sec_per_day  etc...
t_ref = JULDAY( mo_ref, dd_ref, yy_ref,  HH_ref, MM_ref, SS_ref )

convert julian day (t_jul) time into this exotic format (t_xxx)

t_xxx = (t_jul - t_ref) * tics_per_day

and vice versa:

t_jul = t_xxx/tics_per_day + t_ref

Examples are:

  • excel time 1 = days since 1.1.1900 (nineteen hundred)
    (excel has two internal timeformats, you can switch somewhere inside excel between both)
    • t_ref = julday( 1,1,1900, 0,0,0 )
    • tics_per_day = 1.0
    • typical values (june 2023) are in the order of 45087 (45tsnd) days
    • Excel cannot deal with negative times, i.e. dates before t_ref are not valid
    • if you export excel tables as csv you might get this as time variable
  • excel time 2 = days since 1.1.1904 (nineteen O four! )
    • t_ref = julday( 1,1,1904, 0,0,0 )
    • tics_per_day = 1.0
    • typical values (june 2023) are in the order of 43627 (43tsnd) days
    • if you export excel tables as csv you might get this as time variable
    • you can switch somwhere inside excel between both time systems - be careful
    • Excel cannot deal with negative times, i.e. dates before t_ref are not valid
    • Excel has a known bug in the year 1900 it thinks it errornously assumes it was a leap year ⇒ all excel dates before 1.3.1900 might be wrong (weekdays, differences etc.)
  • unix time = seconds since 1.1.1970
    • t_ref = julday( 1,1,1900, 0,0,0 )
    • tics_per_day = 86400 (=24*60*60)
    • typical values (june 2023) are in the order of 1686589673 = 1.67E9 sec
    • this ist the internal time format on unix systems and derivatives (e.g. android)
    • unix time may experience an overflow or a roll over in 2038 when range of long32 is exceeded. This may lead to problems in the computer world.
  • days since 1.1.2000 ( time format in RPG data ? )
    • t_ref = julday(1,1,2000,0,0,0)
    • tics_per_day = 1.
    • typical values (june 2023) are in the order of 8563 days
  • seconds since 1.1.2000 ( time format in Juelich radiation data )
    • t_ref = julday(1,1,2000,0,0,0)
    • tics_per_day = 86400.
    • typical values (june 2023) are in the order of 739905199 = 7.3990510e+008 = 740Mio seconds

day of year, hour of day

The day of the year (doy) - sometimes incorrectly named 'Julian day' - can be calulated from a Julian date by:

CALDAT, t_julian, month, day, year
t_doy = floor( t_julian - JULDAY( 1,1,year ) ) + 1

Note: Day of year starts with 1 on January first ⇒ add one. The simplified formula doy = t_julian - floor(t_julian/days_per_year)*days_per_year - with days_per_year=365.25 does not work - why ?

The hour of the day (hod) is the fractional part of the date as julian day shifted by half a day/twelve hours:

t_hod = ( t_julian-0.5 - floor( t_julian-0.5 ) ) * 24.

or alternatively and more intuitive, but slower

CALDAT, t_julian, month, day, year, hh, mm, ss
t_hod = hh+( mm + ss/60.)/60.

interpolation

IDL has some predefined functions for interpolation:

  • interpol interpolates a 1dim vector to new abscissa values - either on a regular grid or with given abscissas
  • interpolate can do bilinear interpolation on two-dimensional regular or irregular grids
  • bilinear can do bilinear interpolation on regular grids
  • krig2d does 2 dimensional Kriging i.e. a distant weighted interpolation from neighboring points.

The interpolate routine takes only floating point index values for the interpolation. If you want to interpolate to certain nodes x_ipol and y_ipol you have to calculate the respective indices. This can be done by the interpol routine as follows. Assume we have a data array z_org = fltarr(Nx_org,Ny_org) with coordinates x_org=fltarr(Nx_org) and y_org=fltarr(Ny_org) and want interpolate it to z_ipol = fltarr(Nx_ipol,Ny_ipol) with coordinates x_ipol=fltarr(Nx_ipol) and y_ipol=fltarr(Ny_ipol):

; bilinear interpolation 
ix_ipol = interpol( findgen(Nx_org) , x_org , x_ipol )
iy_ipol = interpol( findgen(Ny_org) , y_org , y_ipol )
z_ipol = interpolate( z_org , ix_ipol , iy_ipol , /grid  )

If you want to interpolate only over certain dimensions they must be the last ones. You may use transpose to rearrange. Lets assume you have an array depending on x, y and time, i.e. z=fltarr(Nx,Ny,Nt) and you want to interpolate to certain x and y coordinates you can do this as follows:

z_ipol = transpose( interpolate( transpose(z_org,[2,0,1]) , ix_ipol , iy_ipol , /grid  ), [1,2,0] )

geographical coordinates

IDL can transform geographical coordinates into cartesian x,y coordinates and vice versa. To do that you have to define your coordinatesystem with MAP_PROJ_INIT. To calculate your projected coordinates (X,Y) from longitude and latitude use MAP_PROJ_FORWARD and to get longitude and latitude for a (X,Y) pair use MAP_PROJ_INVERSE.

Example for the universal transverse mercator projection (UTM) for West Germany (UTM zone 32 with central meridian at 9°East):

Init the transformation:

  map_struct = MAP_PROJ_INIT( $
    101 , $ ; UTM
    CENTER_LONGITUDE = longitude , $
    )

The result map_struct contains information of the projection and must be handed to the functions for the caluclation of coordinates. The parameter CENTER_LONGITUDE must be given to define which UTM grid to be used (read wikipedia if you want to understand the details). It should be a longitude close to the locations you are interested in. Following the UTM definition IDL will round it to a multiple of 6° relative to 3° (i.e. CENTER_LONGITUDE=3+n*6 with n an integer). Calcualted UTM coordinates will be conform to the UTM specifaications but the direction of UTM-northing may deviate by several degrees from true north. If you need a coordinate system directed exactly to north at your location use instead:

  map_struct = MAP_PROJ_INIT( $
    109 , $ ; transverse mercator
    DATUM = 8 , $ ; WGS84
    CENTER_LONGITUDE = longitude , $
    FALSE_EASTING = 500000. $
    )

Calculate some UTM values from longitude long and latitude latt:

  UTM = MAP_PROJ_FORWARD( long , latt , map_structure=map_struct  )

where long and latt can be arrays (fltarr(N)) and UTM will be of type fltarr(2,N) containing the calculated UTM coordinates.

The inverse: calculate some longitude and latitude values from UTM=fltarr(2,N)

  LonLat = MAP_PROJ_inverse( UTM, map_structure=map_struct )

A list of the ID's of the projections can be found at MAP_PROJ_INIT help article.

You can also generate a map and draw into this map by using MAP_SET :

  center = [ center_lat , center_lon ]
  d_ll = 30.
  map_box = [ center - [1,1]*d_ll , center + [1,1]*d_ll ]
 
  MAP_SET, /Azimuthal, /isotropic, $
    center_lat , center_lon , 0 , $
    limit = [ lat_a, lon_a, lat_b, lon_b ]
 
  ; project an image to the map with img=bytarr(Nx,Ny)
  read_jpeg, '/data/sfb1211/A01/srtm/other/images/Envisat_global_land_cover_map.jpg', img
  latmin_jpg = -94. &  latmax_jpg = +106. & lonmin_jpg = -180. & lonmax_jpg = +180.
  ; position to put image
  start_x = lonmin_map
  start_y = latmin_map
  ; wrap image - MAP_image can deal only with 2dim array - we need to do it channel by channel
  img_mapped_red = MAP_IMAGE( reform(img[0,*,*]) , startX, startY, lonmin=lonmin_jpg, lonmax=lonmax_jpg, latmin=latmin_jpg, latmax=latmax_jpg )
  img_mapped_grn = MAP_IMAGE( reform(img[1,*,*]) , startX, startY, lonmin=lonmin_jpg, lonmax=lonmax_jpg, latmin=latmin_jpg, latmax=latmax_jpg )
  img_mapped_blu = MAP_IMAGE( reform(img[2,*,*]) , startX, startY, lonmin=lonmin_jpg, lonmax=lonmax_jpg, latmin=latmin_jpg, latmax=latmax_jpg )
  ; put channels togehter to one image
  img_mapped_rgb = transpose( [ [[img_mapped_red]], [[img_mapped_grn]], [[img_mapped_blu]]], [2,0,1] )
  ; put image on canvas
  TV, img_mapped_rgb, startX, startY, true=1
 
 
  ; draw coastlines, continents and Country borders in high resolution 
  MAP_CONTINENTS, /COASTS, /CONTINENTS, /COUNTRIES, /HIRES
 
  ; rivers - idl draw allways all rivers - an hierarchy as in GMT is not there (https://docs.generic-mapping-tools.org/6.3/pscoast.html#i)
  MAP_CONTINENTS, /RIVERS, /HIRES, color = 'ff0000'x
 
  ; draw a grid, make nice black and white axes around it
  MAP_GRID , /box_axes
 
  ; plot and annotate some data (lat=fltarr(N), lon=fltarr(N), name=strarr(N))
  oplot, lon, lat, psym=4
  for i = 0, n_elements(lat)-1 do xyouts, lon[i], lat[i], name[i]
 

wind directions

IDL's atan function can be fed with x and y component of a vector to get the direction angle from -pi to +pi = -180° to +180°.

phi = atan( x, y )

It performs the complete angle determination based on the signs of x and y, i.e. something like

function atan_2, y, x
  ; determination of azimuth angle from x and y components
  ; this is principally done if you call atan( y,x )
if  x eq 0. then begin
  if y gt 0. then phi =  !pi/2. else $
  if y lt 0. then phi = -!pi/2. $
             else phi =  0.0
endif $; x eq 0
else begin ; x ne 0
  phi0 = atan( y/x )
  if (y gt 0. ) and (x gt 0.) then phi = phi0       else $ ; first  quadrant
  if (y le 0. ) and (x gt 0.) then phi = phi0       else $ ; second quadrant
  if (y lt 0. ) and (x lt 0.) then phi = phi0 - !pi else $ ; third  quadrant
  if (y gt 0. ) and (x lt 0.) then phi = phi0 + !pi        ; fourth quadrant
endelse 
return, phi
end ; function 

As the wind directon azimuth angle is differently defined than mathematical azimuth angle i give here the ultimative conversion laws between wind direction azi, wind speed spd and vector components u (W-E) and v (S-N):

; spd and azi  to  u and v
u = - spd * sin(azi*!dtor)
v = - spd * cos(azi*!dtor)
; u and v  to  spd and azi
azi = atan(  -u , -v ) / !dtor
if azi lt 0 then azi += 360.
spd = sqrt( u^2 + v^2 )

Factor !dtor converts degrees to radians, it is of type FLOAT (=single pecision) for DOUBLE precisions use !CONST.DtoR

Examples:

  • North wind: azi = 0° ⇒ u = 0 (no W-E component), v = -spd (wind is blowing from north to south)
  • East wind: azi = 90° ⇒ u = -spd (wind is blowing from East to West), v = 0 (no N-S component)
  • West wind: azi =270° ⇒ u = +spd (wind is blowing from West to east), v = 0 (no N-S component)

Wind direction error

The error can be estimated with gaussian error propagation:

<m>

\phi = arctan(-u/-v)
     = arctan(x)  \ ; \  
x = {-u}/{-v}

</m>

<m>

\Delta \phi^2 = ({d\phi}/du \Delta u)^2 + ({d\phi}/dv \Delta v)^2
             = ({d\phi}/dx dx/du \Delta u)^2 + ({d\phi}/dx dx/dv \Delta v)^2

</m>

if we consider <m>{d\phi}/dx=1/{1+x^2}</m> we get

<m>

\Delta \phi^2 = 1/(u^2+v^2)^2 ( (v \Delta u)^2 + (u \Delta v)^2 )

</m>

percentiles

(or quantiles)

IDL can give you the median but not any other arbitrary percentile. So do it yourself:

; define 5 percentiles
N_perc = 5
p1 = 0.05 ; 5% and 95%
p2 = 0.25 ; 25% and 75% = lower and upper quartiles
; add the median and mirror p1 and p2:
p_fac = [ p1 , p2 , 0.5, 1-p2 , 1-p1 ]

; determine the percentiles
percentiles = (data[ sort[data] ])[ p_fac*(n_elements(data)-1) ]

This way +/-infinity and Nan values are included. IDL sorts them in the following way:

data[sort[data]] = [[-inf.values], [finite.values], [+inf.values], [Nan.values] ]

You have to count the respective non-finite values and include it in the index calculation:

N_neg_inf = long( total(finite(data,/infinity,sign=-1)) )
N_pos_inf = long( total(finite(data,/infinity,sign=+1)) )
N_nan     = long( total(finite(data,/nan             )) )
percentiles = (data[ sort[data] ])[ N_neg_inf + p_fac*(n_elements(data)-1-(N_neg_inf+N_pos_inf+N_nan)) ]

An implementation as function can be found in /home/hatpro/idl/lib/f_percentile.pro

There is no exact method to determine percentiles. Imagine a dataset with 50% of the values lower than a vlaue y1 and the other 50% larger than a value y2 with a large gap between y1 and y2.

  • Where ist the median in this case? Is it y1, y2 or somewhere in between?
  • Is interpolation between y1 and y2 allowed?
  • In the same way any percentile may not be exactly determined
  • wikipedia gives a method for interpolation between these values…

random numbers

Random numbers can be generated with

x = RANDOMU( seed )
x = RANDOMU( seed, N )

for a single number or an array with N elements of Uniform distrinbuted random numbers in [0,1)
Expectation value is thus μ=0.5 and standard deviation σ=3.46.
(Uniform disributed radnom numbers have an expectation value μ=(a+b)/2 and standard deviation σ=sqrt(12)*(b-a) if they lie in the range [a,b))

x = RANDOMN( seed )
x = RANDOMN( seed, N )

for as single number or an array with N elements of Normal distrinbuted random numbers with expectation value μ=0 and standard deviation σ=1.0. There is theoretically no upper and lower border for these random numbers but |x-µ| will probably never be much larger than 4σ.

BUT there is no Zufall in computers !
All these routines produce pseudo random numbers
The random number is calculated from seed !
With every random number a new seed is returned for the next random number …
The following code will allways produce the same sequence of ten “radnom” numbers:

seed = 0
for i =0, 10 do PRINT, RANDOMU(seed)

(you may also omit seed = 0 as idl sets seed than internally to 0)

To generate some randomness you can use the time:

seed = SYSTIME(/seconds)
for i =0, 10 do PRINT, RANDOMU(seed)

See Numerical recipies chapter 7 or wikipedia.

binary-, octal-, decimal-, hexadecimal - numbers

printing

every number can be displayed in many different ways. In IDL this canbe done with formatted output.
Let e.g. x = 16(decimal):

  • binary: print, x, format='(B)' → 10000
  • octal: print, x, format='(O)' → 20
  • decimal: print, x, format='(I)' → 16
  • hexadecimal: print, x, format='(Z)' → 10
  • roman: print, ääääää … ok that does not work - but it should look like XVI

The principle of all these number representations (except for the roman) is positional notation (wikipedia):
A number written as a sequence of digits Ai:
x = A3A2A1A0
is mathemtically the sum:
x = A3*P3 + A2*P2 + A1*P1 + A0*P0

  • Where P is the base of the used number representation.
    • in the decimal system P=10
    • in the binary system P=2
    • in the octal system P=8
    • in the hexadecimalsystem P=16
  • Digits Ai can lie in the interval 0 to P-1.
    • in the decimal system (P=10) valid digits are 0..9
    • in the binary system (P=2) valid digits are 0..1
    • in the octal system (P=8) valid digits are 0..7
    • in the hexadecimalsystem (P=16) valid digits have the decimal values 0..15 - but since digits should be only one character wide the values 10 to 15 are replaced by the first six letters of the alphabet: 10→A, 11→B, 12→C, 13→D, 14→E, 15→F
  • Every position i in the written number is related to the base value Pi
    • the rightmost position in a decimal number has the base value 1 = 100,
      the position to the left has the base value 10 = 101
      the position to the left has the base value 100 = 102
    • the rightmost position in a binary number has the base value 1 = 20,
      the position to the left has the base value 2 = 21
      the position to the left has the base value 4 = 22
    • the rightmost position in a octal number has the base value 1 = 80,
      the position to the left has the base value 8 = 81
      the position to the left has the base value 64 = 82

Since computers do internally work with binary numbers it migth be sometimes useful to write binary numbers - but this creates easily very long numbers. That is the reason to use octal or hexadecimal numbers. Their base is a power of 2 and thus compress some binary digits into one digit. In the octal system 3 binary postions are compressed into one (Poct=8=23) and in the hexadecimal system 4 binary postions are compressed into one (Phex=16=24):

bin   oct      bin  oct ...   bin  hex(dec)    bin   hex(dec)
000    0       100   4       1000   8  (8)    1100    C  (12)
001    1       101   5       1001   9  (9)    1101    D  (13)
010    2       110   6       1010   A (10)    1110    E  (14)
011    3       111   7       1011   B (11)    1111    F  (15)

To convert a number x into any number system on the base P 'manually' (by mental arithmetic ?) divide the number by P and note the divisons rest (a number between 0 and P-1) as the rightmost digit. Continue the same with the result of the division and write the divisons rest to the left of your previous digit until you end up with zero …

constants

To set IDL variables to certain binary, octal or hexadecimal values write the number in the desired representation as string followed by a letter inidcating the used number system:

x = '10000'b
x = '20'O
x = '10'x

converting

To convert strings given in a number system to integer values use reads with an apropriate format specifier:

; binary
s = '10000'
x = 0L
reads, s , x , format='(B)'
print, x
; octal
s = '20'
x = 0L
reads, s , x , format='(O)'
print, x
; hexadecimal
s = '10'
x = 0L
reads, s , x , format='(Z)'
print, x

binary flags

In many cases the status of a system is delivered as flags in a binary number. Every position (i.e. bit OR flag) in the binary number is related to a certain status of a subsystem. A 0 represents false or off or not active etc. A 1 (or a set bit) represents vice versa a true or on or active etc. To check these flags it is convenient to use 'binary' operators (here are operators meant dealing with binary numbers - the same word is used for operators between two numbers…):

  • c = a AND b : if bit at position i is set in a and b it will be set in c too and otherwise not:
    a=1010bin b=0001bin ⇒ c=0000bin=0dec
    a=1010bin b=0010bin ⇒ c=0010bin=2dec
  • c = a OR b : if bit at position i is set in a or b it will be set in c and otherwise not:
    a=1010bin b=0001bin ⇒ c=1011bin=11dec
    a=1010bin b=0010bin ⇒ c=1010bin=10dec
  • c = a XOR b : if bit at position i is set in a only or b only it will be set in c and otherwise not (exclusive or):
    a=1010bin b=0001bin ⇒ c=1011bin=11dez
    a=1010bin b=0010bin ⇒ c=1000bin=8dec
  • c = NOT a : if bit at position i is not set in a it will be set in c and otherwise not:
    a=1010bin ⇒ c=0101bin=5dez

Dont mix these operators with logical operators

To check whether in a status variable stat several flags are set define a mask and check by comparing stat with mask using of the AND operator:

; define a mask to check for bits 2, 6, and 7
mask = '00000100'b OR '01000000'b OR '10000000'b
; check
IF (stat AND mask) ne 0 THEN BEGIN
  print, "some of the flags are set ..."
  ...
ENDIF

hexadecimal representation

in rare cases it might be necessary to see the representation of a variable in memory. This can be done in the following way:

  • copy content of variable into a long or long64 integer variable
  • print it formatted in hex, octal or binary

see https://www.nv5geospatialsoftware.com/Support/Self-Help-Tools/Help-Articles/Help-Articles-Detail/ArtMID/10220/ArticleID/19447/2723

For single precision variables we need a 4Byte=32Bit integer variable i.e. type long. We print in Hexadecimal (formatcode Z), Octal (O) and Binary (B)

x = 1.234
xl = long(x,0)
print, x, xl, xl, xl, f='(f," ",Z," ",O," ",B)'

For double precision variables we need an 8byte = 8*8bit = 64bit integer variable, i.e. Long64:

x = 1.234d
xl = long64(x,0)
print, x, xl, xl, xl, f='(f," ",Z," ",O," ",B)'

This way we can see how NAN's and infinity are represented:

 print, long( !values.f_nan,0),      long64( !values.d_nan,0),      f='(" fnan=",Z,"  dnan=",Z)'
 print, long(-!values.f_nan,0),      long64(-!values.d_nan,0),      f='("-fnan=",Z," -dnan=",Z)'
 print, long( !values.f_infinity,0), long64( !values.d_infinity,0), f='("+finf=",Z," +dinf=",Z)'
 print, long(-!values.f_infinity,0), long64(-!values.d_infinity,0), f='("-finf=",Z," -dinf=",Z)'

will give:

 fnan=    7FC00000  dnan=      7FF8000000000000
-fnan=    FFC00000 -dnan=      FFF8000000000000
+finf=    7F800000 +dinf=      7FF0000000000000
-finf=    FF800000 -dinf=      FFF0000000000000

The -NaN values is a bit strange as NaN is Not-a-Number but IDL also produces NaNs where the sign flag is set.

Fourier Transform

A fourier transform is done with function FFT. to plot the spectrum of a time series y running over a time interval of length T do the following:

FT_y = FFT( y )

N = n_elements( y )
N2 = N/2
i2 = 1+findgen(N2-1)
freq = i2/N * 1/T
FTy_cos = real_part( FT_y[i2] )
FTy_sin = imaginary( FT_y[i2] )
FTy_pow = FT_y[i2] * conj(FT_y[i2])

plot, freq, FTy_cos, color='0000ff'x
plot, freq, FTy_sin, color='ff0000'x
plot, freq, FTy_pow

The power spectrum eventually needs some adjustment - depending on how you define it.

You can use FFT to calculate the auto-covariance and cross-covariance of two time series x and y:

ft_x = FFT( x )
ft_y = FFT( y )
auto_cov_x = FFT( ft_x * CONJ(ft_x) , /INVERSE )
cross_cov_xy = FFT( ft_x * CONJ(ft_y) , /INVERSE )

For long time series this is much faster than a classical sum( xi'*yi' ).

And you can do this also for two dimensional data sets x and y …

wavelet analysis

Wavelet analysis (WV) splits similar to [fast]fouriertransform (FFT) a dataseries into components at different scales (or wavelengths or frequencies). In difference to the FFT it gives also a localization of analysed scales, i.e. it gives information when or where in a dataseries a certain frequency had a a large amlitude.

Idl has a Wavelet Toolkit consisting of a number of graphical user interfaces and functions. It is described in the IDL help under Introduction to the IDL Wavelet Toolkit.

But you need to have a license for the toolkit to use the graphical user interface. We do not have the license!

Nevertheless the basic functions are accessible without the license. In the help there is a list of the functions . Documentation is rather poor sometimes it is helpful to study the source code of the functions you find in the IDL-lib/wavelet/source/ directory (that is in our implementation in /opt/itt/idl/lib/wavelet/source/). It seems as if all the functions were written by Chris Torrence in 2000. He refers in his documentation to

Torrence, C. and G.P. Compo, 1998: A practical guide to wavelet analysis. Bull. Amer. Meteor. Soc., v. 79, pp. 61-78. DOI:10.1175/1520-0477(1998)079<0061:APGTWA>2.0.CO;2

Wavelet transforms

Basically you can do two kinds of wavelet transformations,
the so called discrete wavelet transform (DWT) and the continous wavelet transform (CWT):

  • DWT is based on orthognal wavelets like the Haar or the Daubechie Wavelets. It splits the scale/space domain of the wavelet transform in separate regions with no overlapping information. It will deliver wavelet coefficients in the so called pyramidical order: two coefficents for the lowest scale, four for the second, … and N coefficent for the N'th scale.
    • call with
      wv = WV_DWT( data, Scaling, Wavelet, Ioff, Joff [, ... ] )
      • data = fltarray(N_data) input dataseries
      • scaling = fltarr(N_coeff) scaling coefficients of the wavelet, sometimes called the 'father wavelet', see wavelet functions below
      • wavelet = fltarr(N_coeff) wavelet coefficients, sometimes called the 'mother wavelet', see wavelet functions below
      • Ioff = scalar integer, offset of the supprot for the scaling function, set to -N/2+2 to center it over each point in data
      • Joff = scalar integer, offset of the supprot for the wavelet function, set to -N/2+2 to center it over each point in data
  • CWT can be based on virtually any function (of course there are some restrictions) the best known are the morlet wave package or the mexican hat. The information received by the transformation has some overlap in the scale/space domain. It will deliver the wavelet coefficients as fltarr(N_data,N_scale)
    • call with
      wv = WV_CWT( data, Family, Order [, ... ] )
      • data = fltarray(N_data), input dataseries
      • Family = string, name of the wavelet function. Is expanded to WV_FN_family.PRO, see wavelet functions below
      • Order = float, parameter forwarded to the wavelet function, e.g. the number of zero intercepts of the morlet wavelet.

wavelet functions

IDL provides seven wavelet functions in directory /opt/itt/idl/lib/wavelet/source/ with names

wv_fn_coiflet.pro
wv_fn_daubechies.pro
wv_fn_gaussian.pro
wv_fn_haar.pro
wv_fn_morlet.pro
wv_fn_paul.pro
wv_fn_symlet.pro

These functions return a structure with information about the wavelet. The values of the wavelet funcion itself come in parameter wavelet. Despite their similar names there are two different types of wavelet functions in IDL: DISCRETE and CONTINOUS:

  • discrete are the coiflet, daubechies, haar and symlet wavelet
    • they can be used in the discrete wavelet transform WV_DWT only !
    • they are called by
      wv_info = WV_FN_<name>( [Order, Scaling, Wavelet[, Ioff, Joff]] )
    • the 'mother wavelet' in array wavelet is returned in the third parameter without keyname
    • Scaling is output parameter containing the 'father wavelet' as array
    • wavelet is the wavelet function
  • continous are the gaussian, morlet and paul wavelet
    • they can be used in the continous wavelet transform WV_CWT only !
    • they are called by
      wv_info = WV_FN_<name>( [Order, Scale, N ][, /DOUBLE] [, FREQUENCY=variable] [, /SPATIAL] [, WAVELET=variable])
    • the 'mother wavelt' is returned to the variable specified with keyword WAVELET
    • scale is a scalar input parameter containing the scale of the function (its width if you want)
    • in wavelet is returned the fourier transform of the wavelet function, except when keyword /SPATIAL ist set
    • gaussian is a derivative of the gaussian bell where parameter 'order' is the order of the derivative: 1 is the first derivative, 2 is the second derivative (sometimes called the 'mexican hat wavelet')
    • morlet is a sine with damped amplitude parameter 'order' is called 'nondimensioanl frequency parameter' in the comments of the function. It is the ratio between amplitude damping constant and frequency. It is a measure for the number of full oscialltions.

In principle you can construct your own wavelet function for the continous wavelet transform by writing a function following the name and call conventions:

  • the filename should be WV_FN_<name>.PRO where <name> is then used as paramter family when calling WV_CWT
  • the source code can stand anywhere in the idl path, e.g. in the directory of your current project.
  • for a continous wavelet you must provide parameters Order, Scale, N, and named paramters WAVELET, SPATIAL, DOUBLE and FREQUENCY.
  • the function must return a structure containing information about the function - allthough it is not evaluated in WV_DWT.PRO
  • if flag SPATIAL is set it must return the wavelet
  • if flag SPATIAL is not set it must return the fourier transform of the wavelet and the related frequencies

But so far i get only weird results trying to implement a continous Haar wavelet …

examples

Discrete wavelet transform:

This is a lot of code - mainly to reconstruct the values of scale and time for the wavelet coefficients.

; generate wavelet function
; info record for wavelet function
info_rec = WV_FN_HAAR( 1, scaling, wavelet, ioff, joff)
 
; ioff and joff need to be set !
ioff = 0 ; alternativley -N/2+2
joff = 0 ; alternativley -N/2+2
 
; Compute the discrete wavelet transform
wave_disc = WV_DWT( data, scaling, wavelet,   ioff, joff )
 
; power spectrum if desired
; wave_disc = sqrt(abs(wave_disc))
 
; construct scale and time arrays for the wavelet coefficients
di = N/2
; first two coefficients in wv_disc contain the 'residuums' S0 and S1
time_disc = [ 0 , di ] + di/2
scale_disc = [ di , di ]
; the following two coefficients belong to the largest scale
time_disc  = [ time_disc  , time_disc  ]
scale_disc = [ scale_disc , scale_disc ]
; the following coefficents belong to scales 2^i
while di gt 2 do begin
  di /= 2
  for j = 0L, n-1, di do begin
    time_disc = [ time_disc , di/2+j ]
  endfor ; j
  scale_disc = [ scale_disc , replicate(di,n/di) ]
endwhile
; convert to units of time by multiplying with timestep dt
time_disc = time_disc * dt + time[0]
scale_disc *= dt
 
; make a contour plot 
CONTOUR, $
  wave_disc[2:n-1] , $ ; drop the first two coefficients as they contain the residuums
  time_disc[2:n-1], scale_disc[2:n-1], $ ; reconstructed time and scale from above
   TITLE = 'discrete wavelet transform', $
   XTITLE = 'time', $
   YTITLE = 'period', $  
   /IRREGULAR, $ ; idl will triangulate the data before drawing in a regular grid
   /YLOG , $ ; log scaling on scale axis
   YRANGE = [ max(scale_disc) , min(scale_disc) ], $ ; invert direction of axis
   NLEVELS = 100, $ ; number of isoline levels
   /FILL, $  ; fill isolines with color
   XSTYLE = 1, $
   YSTYLE = 1

Continous wavelet transform

; continous wavelet transform
; wave_cont = WV_CWT(data, 'morlet',   8, /PAD, SCALE=scale_cont )
 
; power spectrum if desired
; wave_cont = sqrt(abs(wave_cont))
 
; convert to units of time by mutliplying with timestep dt
scale_cont *= dt
 
; Contour plot
CONTOUR, wave_cont, time, scale_cont, $
   TITLE = 'continous wavelet transform', $
   XTITLE = 'time', $
   YTITLE = 'period', $  
   /YLOG , $ ; log scaling on scale axis
   YRANGE = [ max(scale_cont) , min(scale_cont) ], $ ; invert direction of axis
   NLEVELS = 100, $ ; number of isoline levels
   /FILL, $  ; fill isolines with color
   XSTYLE = 1, $
   YSTYLE = 1

Errors / debugging

IDL can handle most of arithmetic errors without stopping.

You just get a message at the end of the program like:

% Program caused arithmetic error: Floating divide by 0

or:

% Program caused arithmetic error: Floating illegal operand

The latter might be something like assigning values outside the int-range to an array which has been defined as a=intarr(N).

To debug errors you can set the system variable

!EXCEPT = 2

this forces IDL to print an error message including the line number after each the line where an error occurs.
This should only used for debugging purposes - it slows down IDL by about 5%.

The default is !EXCEPT=1 meaning that error messages are printed at the end of the program. With !EXCEPT=0 no error messages are printed at all.

idl code

native idl routines and examples can be found in the lib directory of the idl distribution. IT can be found by investigating the !PATH variable. Currently it is /opt/itt/idl8.2/idl82/lib/

operating system

For plotting on the screen and for building pathes your code may need to know which operating system your code is running on. You can find this out by reading the !VERSION system variable:

on one of our linux machine it looks like:

IDL> help, !version , /stru
** Structure !VERSION, 8 tags, length=104, data length=100:
   ARCH            STRING    'x86_64'
   OS              STRING    'linux'
   OS_FAMILY       STRING    'unix'
   OS_NAME         STRING    'linux'
   RELEASE         STRING    '8.2.1'
   BUILD_DATE      STRING    'Aug 20 2012'
   MEMORY_BITS     INT             64
   FILE_OFFSET_BITS
                   INT             64

On a windows system it may look like

IDL> Help, !Version, /Structure
 ** Structure !VERSION, 8 tags, length=104, data length=100:
    ARCH            STRING    'x86_64'       OS              STRING    'Win32'
    OS_FAMILY       STRING    'Windows'
    OS_NAME         STRING    'Microsoft Windows'
    RELEASE         STRING    '8.0'
    BUILD_DATE      STRING    'Jun 17 2010'
    MEMORY_BITS     INT             64       FILE_OFFSET_BITS
                    INT             64 

For the correct device To plot on the screen (i.e. open a window to plot in ) you may use:

if !VERSION.OS_FAMILY eq 'Windows' then set_plot, 'Win' $
                                   else set_plot, 'X'

And similar for pathes:

if (!version.os_family eq 'linux')  then $
  srtm_dbase = '/home/jschween/exp/ideen/idl/srtm/srtm/' $
else $ 
  srtm_dbase = 'C:\Users\jschween\Downloads\srtm\'

IDL version

Since November 2012 we have idl 8.2.1

Simply type idl and idl 8.2 will be executed. if no license for idl 8.2 is available 6.4 will be used. if you want to you use 6.4 explicitly, you need to type idl64. idlhelp and idlde will use the 8.2 versions. for 6.4 use idlhelp64 or idlde64, respectively.

To directly start version 6.4. type /opt/itt/idl6.4/idl64/bin/idl

To directly start version 8.2.1. type /opt/itt/idl6.4/idl64/bin/idl

If your code needs to know under which idl version it is running you may use the !VERSION system variable:

if !VERSION.RELEASE ge '8.2.1' then begin
  ...
endif else begin
  ...
endelse

idl license manager

To find out how many users are currently in IDL excute:

/opt/itt/idl8.2/idl82/bin/bin.linux.x86_64/lmstat -c /opt/itt/license/license.dat -f idl

in the listing should appear beside some other ten lines something like:

gop: license server UP (MASTER) v11.6
...
Users of idl:  (Total of 60 licenses issued;  Total of 18 licenses in use)  
...
 jschween qeb qeb/localhost:11 (v8.2) (gop/1700 522), start Wed 11/11 10:48, 6 licenses
...

The line starting with “Users of idl: …” says that there are 18 license in use. As one allways needs six licences when running idl this means that 18/6=3 (i.e. three) instances of idl are running. The line below indicates that user jschween on machine gop is running one instance of idl (using 6 licenses) v8.2 since Nov. 11.

If this is missing and/or you get a long strange output the license manager is probably not running.

To restart the license manager:

  • log in to gop
  • check whether it is running:
ps -elf | grep idl
  • there should appear the licences manager daemon (lmgrd)
  • if not:
sudo /etc/init.d/sys5_idl_lmgrd start

password is your password …

dead IDL processes

If licencses are obviously blocked by dead idl processes (running for several days, see section about license manager above). You may want to kill them (be careful not to destroy hard work of your colleagues !):

  • Login on the respective machine
  • see what idl processes are running by executing:
ps -elf | grep idl
  • output may look like:
...
0 S hatpro   18936     1  0  80   0 -  3513 wait    2019 ?        00:00:00 /bin/bash /usr/local/bin/idl /home/dturner/aerioe/tmp/.run_aerioe.pro
0 R hatpro   18950 18936 98  80   0 - 85711 -       2019 ?        151-17:57:02 /opt/itt/idl8.2/idl82//bin/bin.linux.x86_64/idl /home/dturner/aerioe/tmp/.run_aerioe.pro
...
  • indicating that user hatpro has an idl job ”run_aerioe.pro“ which was started in 2019 and is running for 151 days and ~18hours
  • its process ID is 18950 and it has been started by process 18936
  • eventually check with the (probable) owner whether this is intended
  • to terminate this process do kill 18950 (this is the process ID in this case !)
1)
not a number
2)
see e.g. wikipedia about cross correlation
3)
so they are not really standalone
4)
CF=Climate and forecast
5)
IDL has some odities he !
6)
Grandle and Nystrom, 1980, A method for usage of a large calligraphy set under RSX-11M. Grandle, R.E. and Nystrom, P.A. Proceedings of the Digital Equipment Computer Users Society, November 1980, 391-395.
7)
find it under 'fonts' in the help
8)
y is the position of the base line on which printing occurs. The fraction line is a bit less then half a character height above
internal/administration/idl.1753198010.txt.gz · Last modified: by jan