Go to the previous, next section.

Two-Dimensional Graphics

There is limited support for 2-dimensional graphics in SM, and we do not expect to make many extensions in this area. Currently it is possible to read an unformatted image, to draw contours, to extract values from the image into vectors, and to obtain image values with a cursor. Previous editions of this manual (prior to version 2.3) have traditionally said: `We expect to support half-tone imaging in the nearish future'; as of this release there is a macro (called greyscale and written by Gabor Toth here at Princeton) that draws reasonably efficient grey scale images on any supported output device.

The problem of specifying data formats for images is difficult, and we have adopted an approach of using a `filecap' file to describe the unformatted files. This allows the user to write the file using C or Fortran (or, presumably, lisp) and then use SM to read the data. The name of the entry in the filecap file is given by the variable file_type, which may be given in your `.sm' file if you use the standard startup macro. The format of data on disk is first the x- and y- dimensions of the image, then the data in row-ascending order. The exact statements used to write the data may depend on the chosen value of file_type (or vice-versa). A description of the filecap fields comes at the end of this appendix; most users should never have to change this file.

It is also possible to read data from a formatted file and create an image inside SM, for example if x and y are integers in the range 0-9:

IMAGE (10,10)                   # declare a 10*10 array
READ { x 1 y 2 vals 3 2         # read some data
SET IMAGE(x,y) = vals           # and put it into the image

The command used to read an image is IMAGE, and you may optionally specify the x and y range covered by the data (e.g. IMAGE datafile xmin xmax ymin ymax; this also works with declarations like the one in the previous paragraph). Only the region of the image lying within the current limits is plotted when contouring - just like any other graphics operation in SM. Images may be deleted with the DELETE IMAGE command. To extract values from an image, use the special expression IMAGE(vec_x,vec_y) which has the value of the image at the points (vec_x,vec_y).

You can extract variables from the image's header using the command DEFINE name IMAGE. See the discussion of filecap if you need to know what variables can be retrieved this way.

If you want to use non-interactive SM (i.e. write your own command interpreter), there is a call (defimage) to define your data array to the contouring package, but this is of course not available interactively.


Filecap is similar in graphcap or the Unix termcap, and in fact the `filecap' entries may be physically in the same file as graphcap. Filecap can be specified as a list of files to be searched in order. The fields in filecap are used to specify the data type of the file, the record format of the file (record-length, inter-record gaps, etc.) and the header used (length of header, offset of desired items). Note that these are `unformatted' files, as written by C write() or fortran write(num) statements.

The current filecap is as follows:

# Filecap describes unformatted file formats to SM. The syntax is
# identical to termcap or graphcap files.
c|C|C files:\
ch|CH|C files with headers:\
fits|cfits|FITS|CFITS|C FITS files:\
no_header|Files with no header, will prompt for nx, ny:\
unix|UNIX|Fortran unformatted files under Unix on a Vax:\
unix_int|UNIX_INT|Like unix, but integer*4:\
unix_short|UNIX_SHORT|Like unix, but integer*2:\
vms_var|VMS_VAR|Fortran unformatted under VMS, recordtype=variable:\
vms_fixed|VMS_FIXED|Fortran unformatted under VMS, recordtype=fixed:\
# This seems to be correct, based on one example
vms_direct:VMS_DIRECT|Fortran unformatted, direct access, under vms:\
Comment lines start with a #, continuation lines start with white space (a tab or a space) and \ may be used to continue an entry on to the next line. The first few fields in an entry are separated by |, and are alternative names for the same entry. For example, fortran unformatted files under Unix may be referred to as unix or UNIX. Fields are separated by colons, and are of the form :CC#nnn: for numbers, and :SS=str: for strings. Omitted fields may be specified as :CC@nnn:, or simply omitted. Filecap capabilities currently used are:
DA (DAta type)
Type of data in file (string)
FS (File Start)
Unwanted bytes at start of file
HS (Header Size)
Size of header
RE (Record End)
Unwanted bytes at end of record
NS (No Swap)
Don't swap bytes for FITS files
RL (Record Length)
Number of useful bytes per record
RS (Record Start)
Unwanted bytes at start of record
SW (SWap)
Byte swap 2by integer data, and byte-and-word swap 4by integers.
nx (Number X)
Offset in file of X size of data
ny (Number Y)
Offset in file of Y size of data
x0 (X 0)
Coordinate at start of X axis
x1 (X 1)
Coordinate at end of X axis
y0 (Y 0)
Coordinate at start of Y axis
y1 (Y 1)
Coordinate at end of Y axis
In addition, HH is supported as an archaic form of HS.

In terms of these quantities a file will look like this: As mentioned below, HS can be negative and is then taken as the record length of the header RL_H, in which case the file will be like: Note that the first real data record begins with an RS -- this means that if you are writing fortran you must write the header in a different write statement than the one that you use to write the data (i.e. write(fd) nx,ny,arr will not work).

Most parameters are optional, and will default to 0. If RE or RS is specified, you must give RL as well. If you specify it as -ve, then we'll look for it from the operating system. You must provide a value for HS (or HH if you're old fashioned), if it is negative we'll assume that even the header has a record structure, with RS and RE just like any other record. Its record length will be taken to be RL, if RL is positive, otherwise we'll find it from the operating system. If HS and RL are both negative there is no reason why the record length of the header should be the same as that of the data.

If neither nx nor ny are present in the graphcap entry you will be prompted@footnote #{in fact they will be read from a macro or the command line without prompting if they are available; try define file_type no_header image file \n 10 20} for the x and y dimensions of the file, otherwise they must both be present. If they are negative they are taken to be the negation of the true dimension (so a filecap entry :ny#-6: specifies that the y-dimension of the data is 6), but usually they are taken as the offsets of the values of the x and y dimensions in the file, relative to the start of the header (if you set HS to be zero you can specify nx and ny on the command line, but they must be on a separate line, either a real separate line, or following a \n). Note that HS excludes FS, so HS will usually be 2*sizeof(int) irrespective of the value of FS, and nx will usually be 0. If HS is negative, then the nx and ny offsets also ignore RS, i.e. nx is still usually 0.

As an alternative to specifying the actual file sizes as negative integer values of nx or ny (e.g. :nx#-10:) you can simply specify them as positive string values: :nx=10:. Note how this differs from :nx#6:.

Possibilities for DA are char, float (default), int, long, and short, all as in C, and also fits for FITS format data. (If you don't know what FITS format is, don't worry about it. It's a style of header and record structure used for data transport in astronomy. If you do know about FITS, then we assume that each record is 2880by long, although possibly with RS and RE non-zero. The header is processed for the size of the file and the value of BITPIX. If the file is not SIMPLE, it is taken to be 4by floating point data. CRPIX, CRVAL, and CDELT are interpreted correctly, as are BZERO and BSCALE. X0, x1, y0, and y1 are specified as the keywords X0, X1, Y0, and Y1 respectively, and take preference over CRPIX etc. FITS files on a machine with vax byte order are supposed to be byte swapped, but you can override this by specifying the NS capability which stops SM from doing any byte swapping). If you specify SW it takes preference over any other instructions about byte swapping and forces byte-swapping for 2byte (short) data, and byte-and-word swapping for 4byte (int) data. If x0 and x1, or y0 and y1 are omitted, the range of values on the appropriate axis is taken to be 0 to nx-1 (or ny-1). You can override these values with the IMAGE command. The values of X0 (and so on) can be obtained directly using DEFINE X0 IMAGE; Other values can be specified in `filecap', for example a filecap entry :aa#24: specifies that DEFINE aa IMAGE should recover the (floating-point) number stored at byte offset 24. Because of the way that filecap files work, you are restricted to two-character names. If you are using FITS files, all the keywords from the header are available, but you should remember that SM is case sensitive.

For example, suppose I wrote a file using the Unix f77 compiler, with some code that looked like:

        integer *4 nx,ny,arr(10,20)
        nx = 10
        ny = 20
        write(8) nx,ny
        write(8) arr
(Omitting opening unit 8 as an unformatted file, and filling the array with data). Then I could read it in SM by defining file_type to be unix_int. The filecap entry indicates that the length of the header is to be obtained from unix (in fact from the file, it'll be the record length of the header), as is the record length. Both the start-of-record (RS) and end-of-record (RE) gaps are 4by long (they in fact contain the record length, but you needn't know that). The number of x-records is at zero offset in the file, and y-records is at offset 4. In other words, allowing for FS being 0 and RS being 4, nx occupies bytes 4-7 in the file, and ny 8-11. The data is taken to be 4-byte integers (integer*4 to fortran). In fact, the first record consists of only the values of nx and ny, so the record length of the first record is 8by, and part of an equivalent filecap entry would be@br
where I have interpreted RS as FS, and added the RE onto the end of the header length HS. If I had written the data out line-by-line in a do loop, the file type would still be unix_int, but the record length actually used by SM in reading the file would be different (@xref{Image}).

As another example, I use an image processing system called Wolf that used to use files with the arcane structure of 200bytes of header, then the x- and y-size of the file, given as ints, then more header up to a total offset of 1024 bytes from the start of the file, then the data written as short integers. I could write a header with code that looked somewhat like (omitting all error checking):

        int fd,i;
        int xs = 20,ys = 10;
        short arr[10][20];
        write(fd,(char *)&xs,sizeof(int);
        write(fd,(char *)&ys,sizeof(int);
        for(i = 0;i < ys;i++) write(fd,(char *)arr[i],xs*sizeof(short));
The corresponding filecap entry is
wolf|Wolf-IfA files:\
which is pretty simple really.

Go to the previous, next section.