3 The basics of the IMG library

 3.1 Example 1 – A “do nothing" code
 3.2 Example 2 – Opening and closing files
 3.3 About the IMG library
 3.4 Really getting to your data (or data mapping)
 3.5 Putting it all together – Complete applications
 3.6 Example 3 – Processing data values
 3.7 Output from applications
 3.8 Example 4 – Updating a file
 3.9 Example 5 – Creating a new file

Note – The more experienced programmer might find the first couple of examples in this section worth skipping. In this section, you’ll begin to write your own applications. We’ll start very slowly by looking at a traditional “do nothing” code.

3.1 Example 1 – A “do nothing" code

Let’s get started on your first application. Enter the following code via your choice of text editor. Save it as nowt.f.

        SUBROUTINE NOWT(STATUS)
  C This application does nothing. It is an inapplicable application.
        CONTINUE
        END

You’ll need an interface file as well, despite the lack of code, as it is required by alink. Again, enter this with your text editor, but this time save it as nowt.ifl

  interface nowt
  endinterface

Now we can compile and link the code in the following way. From the UNIX prompt type:

  % alink nowt.f

This should create an executable file called nowt. Try running nowt. If you’ve done everything correctly, nowt happens. While this is a good sign, it’s hardly gripping stuff. You could always edit the code so it reads:

        SUBROUTINE NOWT(STATUS)
  C This application does nothing. But at least you know it’s friendly.
        WRITE (*,*) ’Hello World!’
        CONTINUE
        END

Note that you can run alink to recompile nowt.f without changing the interface file. This is because the nowt application still doesn’t require any parameters to work.

3.2 Example 2 – Opening and closing files

Let’s now progress to a code which opens a Starlink (.sdf) data file. Enter the following code and save it as sesame.f

         SUBROUTINE SESAME(STATUS)
  C This application opens a file and promptly closes it again.
         INTEGER NX, NY, IP, STATUS
         CALL IMG_IN(’IN’,NX,NY,IP,STATUS)
         CALL IMG_FREE(’IN’,STATUS)
         END

And of course we’ll need the obligatory interface file sesame.ifl

  interface SESAME
    parameter IN
      prompt ’Input Image’
    endparameter
  endinterface

Now, to compile sesame use:

  % alink sesame.f -L/star/lib ‘img_link_adam‘

If you get error messages, first make sure you have started the Starlink software on your session. If you still have trouble, try the command star_dev. (This sets up a link to the Starlink libraries – don’t worry too much about how it works). Note the change in the alink command from the previous ones.

Now try running your code. You should get:

  % sesame
  IN - Input image >

Assuming your code works(!), you can give the name of an NDF file which it will read and promptly do nothing. This code appears to be another “do nothing” code. It isn’t, so let’s take a closer look at what’s going on.

Firstly, we start the application with the usual

  SUBROUTINE SESAME(STATUS)

It is a convention when writing ADAM applications to always have a subroutine (of the same name as the application) with an integer argument instead of the more familiar

  PROGRAM SESAME

statement. Don’t worry too much about why this should be the case. The next line merely declares some variables. Now we get to:

  CALL IMG_IN(’IN’,NX,NY,IP,STATUS)

This line opens the file you told it to. It associates the file with a parameter called IN. In order to get information about the parameter IN, the application looked in the interface file. It was told that for this parameter it would need to prompt the user into providing a file name by using a message.

IMG_IN also found out some information about the image. Its size is returned via the arguments NX and NY. The next variable (IP) is a pointer. We will return to this concept in the next section. STATUS is used to check all is well.

The next call,

  CALL IMG_FREE(’IN’,STATUS)

simply closes the image associated with the parameter IN and tidies up. Applications which call more than one image must apply this call to all the images used.

3.3 About the IMG library

In the last section, we used two subroutine calls of the form CALL IMG_ These subroutines are part of the IMG library. You can find out all about the facilities offered by IMG by reading SUN/160.

IMG is a collection of software designed to make the task of reading astronomical data easy. It allows access to both the data itself and the header information contained within the files. It can be called from both Fortran and C codes (we will stick to the former) and forms part of the NDF library. The NDF library is somewhat more complex (and somewhat more powerful) than the IMG library, so let’s save any more discussion on it until later sections.

When the alink command was used in the last section,

  % alink sesame.f -L/star/lib ‘img_link_adam‘

you explicitly told alink that you needed to link sesame.f to the IMG library. You must make absolutely sure that every time you use the IMG library, you inform alink in this way! This also goes for any other Starlink libraries you use (as we will do later on in this book).

3.4 Really getting to your data (or data mapping)

Opening and closing files is fine, but what you really want to be able to do is get to your data. ADAM libraries have a particular way of handling arrays of data. Remember how in sesame.f you used:

  CALL IMG_IN(’IN’,NX,NY,IP,STATUS)

but we didn’t discuss the integer variable IP? Well, this is related to data access. Specifically, IP acts as a pointer to the array of data in the file. Users of the C programming language will be familiar with the concept of pointers, but Fortran programmers will generally have little or no experience of them. No problem, you don’t need to know the ins and outs of how they work. Put simply, a pointer tells a piece of code where some information is kept in the memory of the machine. In the case of the applications we’re going to be looking at in a minute, we’ll use pointers to “map” how the data contained in a file is held in the memory of your machine.

3.5 Putting it all together – Complete applications

In this section, we’ll go through two applications. The first demonstrates how data in a file is mapped into an array of values. The second covers the methods of creating new files and updating old files.

3.6 Example 3 – Processing data values

Type in the following code and save it as stats.f

         SUBROUTINE STATS(STATUS)
  C This program works out the mean, the minimum and the maximum
  C values in the data array of a file.
         INTEGER NX, NY, IP, STATUS, I, J
         REAL MIN, MAX, MEAN, SUM
  C
         CALL IMG_IN(’IN’,NX,NY,IP,STATUS)
         CALL DOSTAT(%VAL(IP),NX,NY,STATUS)
         CALL IMG_FREE(’IN’,STATUS)
         END
  
         SUBROUTINE DOSTAT(IMAGE,NX,NY,STATUS)
  C Make sure SAE_PAR is available
         INCLUDE ’SAE_PAR’
         REAL IMAGE(NX,NY), SUM, MIN, MAX
  C
         IF (STATUS .NE. SAI__OK) RETURN
  C Initialise the variables we’ll need.
         SUM = 0.0
         MAX = IMAGE(1,1)
         MIN = IMAGE(1,1)
  C Now loop around all the points in the data
         DO I = 1, NX
           DO J = 1, NY
             SUM = SUM + IMAGE (I,J)
             IF (IMAGE(I,J) .GT. MAX) MAX = IMAGE(I,J)
             IF (IMAGE(I,J) .LT. MIN) MIN = IMAGE(I,J)
           END DO
         END DO
  C Print the results
         WRITE (*,*) ’The size of the image is ’,NX,’ by ’,NY,’ pixels’
         WRITE (*,*) ’ ’
         WRITE (*,*) ’The mean of the image is ’, SUM/REAL(NX*NY)
         WRITE (*,*) ’’
         WRITE (*,*) ’The minimum and maximum are ’,MIN,’ and ’,MAX
         END

Have a go at writing your own interface file for stats and compile it with alink. Finally, try running it on an NDF (i.e. .sdf) file of your choice.

Let’s have a careful look at how this code is constructed. The main body of the code consists of opening an image, calling a subroutine which does the actual work, followed by a call to close the image and clean up. You’ll find this structure in a lot of Starlink software. It helps to keep the code uncluttered and is easy to maintain, so we recommend that you try to stick to it.

The call to the DOSTAT subroutine shows how pointers are used to access the data in the file. The %VAL statement is a VAX extension to Fortran. Normally, when a Fortran code uses arrays, it needs to have some information about the size of the array right from the outset. There is no method of doing memory allocation “on the fly”. (This is not true of Fortran 90, but let’s restrict ourselves to using Fortran 77 for this guide). This just isn’t practical for applications which have to deal with many different sizes of images.

The %VAL method makes the pointer (in this case IP) look just like an array. What’s more, it makes sure that the array is of the right size, in this case NX by NY pixels. This is not a feature of “standard” Fortran 77, but unless you hear otherwise, it should be OK on your machine.

You’ll see that when the subroutine DOSTAT inherits the %VAL, it does so with a ready-made array of real values. This way, there is no need to pre-define your array sizes.

3.7 Output from applications

When you want to write out a data file you want to do one of two things. Either you want to write out the same data file you read in, presumably with some modification, or you want to create an entirely new file. The IMG library quite happily lets you do both. The next two codes describe each of these processes in turn.

3.8 Example 4 – Updating a file

Enter the code for clip.f:

         SUBROUTINE CLIP(STATUS)
  C This application sets all the data values above and
  C below two thresholds to zero.
         CALL IMG_IN(IMAGE,NX,NY,IP,ISTAT)
         WRITE (*,*) ’Enter the maximum data value permitted >’
         READ (*,*) MAX
         WRITE (*,*) ’Enter the minimum data value permitted >’
         READ (*,*) MIN
  C
  C Make sure we haven’t done anything really dumb
  C
         IF (MAX .LE. MIN) THEN
  C
  C Oh dear...
  C
           WRITE (*,*) ’Doh!!!!’
           RETURN
         END IF
  C
  C Open the image, but do so in such a way that it can be
  C modified rather than kept the same.
  C
         CALL IMG_MOD(’IN’,NX,NY,IP,STATUS)
  C
  C Now clip off the values which are higher and lower than
  C the requested values.
  C
         CALL CLIPIT(%VAL(IP),NX,NY,MAX,MIN,NCLIP,STATUS)
  C
  C Let the user know how many pixels were reset.
  C
         WRITE (*,*) NCLIP,’ values were reset.’
  C
  C Tidy up.
  C
         CALL IMG_FREE(’IN’,STATUS)
         END
  
         SUBROUTINE CLIPIT(IMAGE,NX,NY,MIN,MAX,NCLIP,STATUS)
  C
         REAL IMAGE (NX,NY)
         REAL MIN, MAX
         INTEGER NX, NY, STATUS, NCLIP
  C
  C Make sure SAE_PAR is available
  C
         INCLUDE ’SAE_PAR’
  C
  C Is everything OK?
  C
         IF (STATUS .NE. SAI__OK) RETURN
  C
  C Get clipping!
  C
         NCLIP = 0
         DO I = 1, NX
           DO J = 1, NY
             IF (IMAGE(I,J).LT.MIN .OR. IMAGE(I,J).GT.MAX) THEN
             IMAGE (I,J) = 0.0
             NCLIP = NCLIP + 1
           END DO
         RETURN
         END

and the corresponding interface file clip.ifl:

  interface CLIP
    parameter IN
      prompt ’Image to clip’
    endparameter
  endinterface

and compile it using alink, remembering to link to the IMG library.

You can check that your code has worked by using the stats application from the last section. You should see how the reported maximum and minimum data values have changed if you chose your clipping limits appropriately.

This code modifies your input file, i.e. no new file is created. This is often not the most useful form of output as, especially in data reduction, you might want to repeat a step with the original data many times. In such a case, it is more useful to preserve the input data and create a new file from scratch. This next code demonstrates how this is done using the IMG library.

3.9 Example 5 – Creating a new file

Enter the following code for bigger.f.

        SUBROUTINE BIGGER(STATUS)
  C
  C This program goes through two images pixel by pixel. It writes
  C the largest value of two pixels in the same parts of the two
  C images to a third image. If the first two images are not the
  C same size then the program quits.
  C
  C Read in the two input images.
  C
        CALL IMG_IN(’IN1,IN2’,NX1,NY1,IP1,STATUS)
  C
  C Make an output image, modelled on the first input image
  C
        CALL IMG_OUT(’IN1’,’OUT’,IPOUT,ISTAT)
  C
  C Do the comparison
  C
        CALL BIG(%VAL(IP1),%VAL(IP2),%VAL(IPOUT),NX,NY,STATUS)
  C
  C Tidy up and close
  C
        CALL IMG_FREE(’*’,STATUS)
        END
  
        SUBROUTINE BIG(IMAGE1,IMAGE2,IMAGE3,NX,NY,STATUS)
  C
        REAL IMAGE1(NX,NY)
        REAL IMAGE2(NX,NY)
        REAL IMAGE3(NX,NY)
        INTEGER NX, NY, STATUS, I, J
  C
        DO I = 1, NX
          DO J = 1, NY
            IF (IMAGE1(I,J) .GT. IMAGE2(I,J)) THEN
              IMAGE3(I,J) = IMAGE1(I,J)
            ELSE
              IMAGE3(I,J) = IMAGE2(I,J)
            ENDIF
          END DO
        END DO
        RETURN
        END

Notice how the code reads two distinct images in with a single statement.

The interface file, bigger.ifl, looks a little different to that which you have already seen:

  interface BIGGER
  
    parameter IN1
      prompt ’Enter first file name’
    end parameter
  
    parameter IN2
      prompt ’Enter second file name’
    end parameter
  
    parameter OUT
      prompt ’Enter output file name’
    end parameter
  
  endinterface