Application Program Interface

From NeXus
Jump to: navigation, search

The Application Program Interface (API) has been developed to facilitate the reading and writing of NeXus files. Those writing applications to produce NeXus files are encouraged to use the API in order to ensure compliance with the NeXus standard. The API supports the reading and writing of HDF4, HDF5, and, since version 3.0.0, XML files.

The core routines have been written in C but wrappers are available for a number of other languages including Fortran 77, Fortran 90, Java, and IDL (with Python and others under development). The API makes the reading and writing of NeXus files transparent; the user doesn't even need to know the underlying format when reading a file since the API calls are the same.

Purpose of API

The NeXus Application Program Interface is a suite of subroutines, written in C but with wrappers in Fortran 77 and 90. The subroutines call HDF routines to read and write the NeXus files with the correct structure. An API serves a number of useful purposes:

  1. It simplifies the reading and writing of NeXus files.
  2. It ensures a certain degree of compliance with the NeXus standard.
  3. It allows the development of sophisticated input/output features such as automatic unit conversion. This has not been implemented yet.
  4. It hides the implementation details of the format. In particular, the API can read and write HDF4, HDF5 (and shortly XML) files using the same routines.

For these reasons, we request that all NeXus files are written using the supplied API. We cannot be sure that anything written using the underlying HDF API will be recognized by NeXus-aware utilities.

Core API

The core API provides the basic routines for reading, writing and navigating NeXus files. It is designed to be modal; there is a hidden state that determines which groups and data sets are open at any given moment, and subsequent operations are implicitly performed on these entities. This cuts down the number of parameters to pass around in API calls, at the cost of forcing a certain pre-approved mode d'emploi. This mode d'emploi will be familiar to most: it is very similar to navigating a directory hierarchy; in our case, NeXus groups are the directories, which contain data sets and/or other directories.

The core API comprises the following functional groups:

  • General initialization and shutdown: opening and closing the file, creating or opening an existing group or dataset, and closing them.
  • Reading and writing data and attributes to previously opened datasets.
  • Routines to obtain meta-data and to iterate over component datasets and attributes.
  • Handling of linking and group hierarchy.
  • Routines to handle memory allocation.

C and C++ Interface

Doxygen documentation for C and C++routines

Fortran 77 Interface

Wrapper routines to interface the Fortran and C code have been developed by Freddie Akeroyd. The routines have the same names and argument lists as the corresponding C routines, although we have added extra routines for the input/output of character data and attributes. Care must be taken to ensure enough space is allocated for the input/output operations being performed.

It is necessary to reverse the order of indices in multidimensional arrays, compared to an equivalent C program, so that data are stored in the same order in the NeXus file.

Any program using the F77 API needs to include the following line near the top in order to define the required constants (NXHANDLESIZE, NXLINKSIZE, etc.) :

include 'NAPIF.INC'

Use the following table to convert from the C data types listed with each routine to the F77 data types.

C Fortran 77
int a, int* a INTEGER A
char* a CHARACTER*(*) A
int[] a INTEGER A(*)
void* a REAL A(*) or DOUBLE A(*) or INTEGER A(*)

Also see doxygen documentation

Fortran 90 Interface

The Fortran 90 interface is a wrapper to the C interface with nearly identical routine definitions. As with the Fortran 77 interface, it is necessary to reverse the order of indices in multidimensional arrays, compared to an equivalent C program, so that data are stored in the same order in the NeXus file.

Any program using the F90 API needs to put the following line at the top (after the PROGRAM statement) :

use NXmodule

Use the following table to convert from the C data types listed with each routine to the Fortran 90 data types.

C Fortran 90
int, int* integer
char* character(len=*)
NXhandle, NXhandle* type(NXhandle)
NXstatus integer
int[] integer(:)
void* real(:)
NXlink a, NXlink* a type(NXlink)

The following parameters, which are defined in NXmodule, may be used in defining variables.

Name Description Value
NX_MAXRANK Maximum number of dimensions 32
NX_MAXNAMELEN Maximum length of NeXus name 64
NXi1 Kind parameter for a 1-byte integer selected_int_kind(2)
NXi2 Kind parameter for a 2-byte integer selected_int_kind(4)
NXi4 Kind parameter for a 4-byte integer selected_int_kind(8)
NXr4 Kind parameter for a 4-byte real kind(1.0)
NXr8 Kind parameter for an 8-byte real kind(1.0D0)

Also see doxygen documentation

Java Interface

NeXus for Java provides access to NeXus data files for programs written in Java. This API was implemented by Java code calling the original C language NeXus API through the Java Native Methods Interface.

Python Interface

Documentation available in pydoc and doxygen

IDL Interface

IDL is an interactive data evaluation environment developed by Research Systems - it is an interpreted language for data manipulation and visualization. The NeXus IDL bindings allow access to the NeXus API from within IDL - they are installed when NeXus is compiled from source after being configured with the following options

./configure  --with-idlroot=/path/to/idl/installation  --with-idldlm=/path/to/install/dlm/files/to

For further details see the IDL binding README

Utility API

The NeXus F90 Utility API provides a number of routines that combine the operations of various core API routines in order to simplify the reading and writing of NeXus files. At present, they are only available as a Fortran 90 module but a C version is in preparation.

The utility API comprises the following functional groups:

  • Routines to read or write data.
  • Routines to find if groups, data, or attributes exist, and to find data with specific signal or axis attributes i.e. to identify valid data or axes.
  • Routines to open other groups to which NXdata items are linked, and to return again.

Any program using the F90 Utility API needs to put the following line near the top of the program (N.B. do not put USE statements for both NXmodule and NXUmodule; the former is included in the latter) :

use NXUmodule

List of Routines

Reading and Writing
NXUwriteglobals Writes all the valid global attributes of a file.
NXUwritegroup Opens a group (creating it if necessary).
NXUwritedata Opens a data item (creating it if necessary) and writes data and its units.
NXUreaddata Opens and reads a data item and its units.
NXUwritehistogram Opens one dimensional data item (creating it if necessary) and writes histogram centers and their units.
NXUreadhistogram Opens and reads a one dimensional data item and converts it to histogram bin boundaries.
NXUsetcompress Defines the compression algorithm and minimum dataset size for subsequent write operations.
Finding Groups, Data, and Attributes
NXUfindclass Returns the name of a group of the specified class if it is contained within the currently open group.
NXUfinddata Checks whether a data item of the specified name is contained within the currently open group.
NXUfindattr Checks whether the currently open data item has the specified attribute.
NXUfindsignal Searches the currently open group for a data item with the specified SIGNAL attribute.
NXUfindaxis Searches the currently open group for a data item with the specified AXIS attribute.
Finding Linked Groups
NXUfindlink Finds another link to the specified NeXus data item and opens the group it is in.
NXUresumelink Reopens the original group from which NXUfindlink was used.

Currently, the F90 utility API will only write character strings, 4-byte integers and reals, and 8-byte reals. It can read other integer sizes into four-byte integers, but does not differentiate between signed and unsigned integers. Here are two example programs which make heavy use of the Utility API.

NXbrowse.f90 provides a simple terminal browser of any NeXus file. When compiled and linked with the NeXus API, it can be run interactively. Type HELP to obtain a list of available commands. (Please note that this version of the browser has now been superceded by a version written in C. Only the C version is now included in the Makefile distributed with the API.)

NXlrcs.f90 is a program for converting IPNS data from the LRMECS chopper spectrometer into NeXus format. It cannot be run without linking to the IPNS run-file modules (not provided), but gives an example of how to write such programs.

Example NeXus program

The following code reads a two-dimensional set 'counts' with dimension scales of 't' and 'phi' using local routines, and then writes a NeXus file containing a single NXentry group and a single NXdata group. This is the simplest data file that conforms to the NeXus standard.

C Version

#include "napi.h"

int main()
    int counts[50][1000], n_t=1000, n_p=50, dims[2], i;
    float t[1000], phi[50];
    NXhandle file_id;
 * Read in data using local routines to populate phi and counts
 * for example you may create a getdata() function and call
 *      getdata (n_t, t, n_p, phi, counts);
/* Open output file and output global attributes */
    NXopen ("NXfile.nxs", NXACC_CREATE5, &file_id);
      NXputattr (file_id, "user_name", "Joe Bloggs", 10, NX_CHAR);
/* Open top-level NXentry group */
      NXmakegroup (file_id, "Entry1", "NXentry");
      NXopengroup (file_id, "Entry1", "NXentry");
/* Open NXdata group within NXentry group */
        NXmakegroup (file_id, "Data1", "NXdata");
        NXopengroup (file_id, "Data1", "NXdata");
/* Output time channels */
          NXmakedata (file_id, "time_of_flight", NX_FLOAT32, 1, &n_t);
          NXopendata (file_id, "time_of_flight");
            NXputdata (file_id, t);
            NXputattr (file_id, "units", "microseconds", 12, NX_CHAR);
          NXclosedata (file_id);
/* Output detector angles */
          NXmakedata (file_id, "polar_angle", NX_FLOAT32, 1, &n_p);
          NXopendata (file_id, "polar_angle");
            NXputdata (file_id, phi);
            NXputattr (file_id, "units", "degrees", 7, NX_CHAR);
          NXclosedata (file_id);
/* Output data */
          dims[0] = n_t;
          dims[1] = n_p;
          NXmakedata (file_id, "counts", NX_INT32, 2, dims);
          NXopendata (file_id, "counts");
            NXputdata (file_id, counts);
            i = 1;
            NXputattr (file_id, "signal", &i, 1, NX_INT32);
            NXputattr (file_id, "axes",  "polar_angle:time_of_flight", 26, NX_CHAR);
          NXclosedata (file_id);
/* Close NXentry and NXdata groups and close file */
        NXclosegroup (file_id);
      NXclosegroup (file_id);
    NXclose (&file_id);

Fortran 77 Version

      program WRITEDATA
      include 'NAPIF.INC'
      integer*4 status, file_id(NXHANDLESIZE), counts(1000,50), n_p, n_t, dims(2)
      real*4 t(1000), phi(50)

!Read in data using local routines
      call getdata (n_t, t, n_p, phi, counts)
!Open output file
      status = NXopen ('NXFILE.NXS', NXACC_CREATE, file_id)
        status = NXputcharattr 
     +         (file_id, 'user', 'Joe Bloggs', 10, NX_CHAR)
!Open top-level NXentry group
        status = NXmakegroup (file_id, 'Entry1', 'NXentry')
        status = NXopengroup (file_id, 'Entry1', 'NXentry')
!Open NXdata group within NXentry group
          status = NXmakegroup (file_id, 'Data1', 'NXdata')
          status = NXopengroup (file_id, 'Data1', 'NXdata')
!Output time channels
            status = NXmakedata 
     +         (file_id, 'time_of_flight', NX_FLOAT32, 1, n_t)
            status = NXopendata (file_id, 'time_of_flight')
              status = NXputdata (file_id, t)
              status = NXputcharattr 
     +         (file_id, 'units', 'microseconds', 12, NX_CHAR)
            status = NXclosedata (file_id)
!Output detector angles
            status = NXmakedata (file_id, 'polar_angle', NX_FLOAT32, 1, n_p)
            status = NXopendata (file_id, 'polar_angle')
              status = NXputdata (file_id, phi)
              status = NXputcharattr (file_id, 'units', 'degrees', 7, NX_CHAR)
            status = NXclosedata (file_id)
!Output data
            dims(1) = n_t
            dims(2) = n_p
            status = NXmakedata (file_id, 'counts', NX_INT32, 2, dims)
            status = NXopendata (file_id, 'counts')
              status = NXputdata (file_id, counts)
              status = NXputattr (file_id, 'signal', 1, 1, NX_INT32)
              status = NXputattr
     +          (file_id, 'axes', 'polar_angle:time_of_flight', 26, NX_CHAR)
            status = NXclosedata (file_id)
!Close NXdata and NXentry groups and close file
          status = NXclosegroup (file_id)
        status = NXclosegroup (file_id)
      status = NXclose (file_id)


Fortran 90 Version

   use NXUmodule

   type(NXhandle) :: file_id
   integer, pointer :: counts(:,:)
   real, pointer :: t(:), phi(:)

!Use local routines to allocate pointers and fill in data
   call getlocaldata (t, phi, counts)
!Open output file
   if (NXopen ("NXfile.nxs", NXACC_CREATE, file_id) /= NX_OK) stop
   if (NXUwriteglobals (file_id, user="Joe Bloggs") /= NX_OK) stop
!Set compression parameters
   if (NXUsetcompress (file_id, NX_COMP_LZW, 1000) /= NX_OK) stop
!Open top-level NXentry group
   if (NXUwritegroup (file_id, "Entry1", "NXentry") /= NX_OK) stop
   !Open NXdata group within NXentry group
      if (NXUwritegroup (file_id, "Data1", "NXdata") /= NX_OK) stop
   !Output time channels
         if (NXUwritedata (file_id, "time_of_flight", t, "microseconds") /= NX_OK) stop
   !Output detector angles
         if (NXUwritedata (file_id, "polar_angle", phi, "degrees") /= NX_OK) stop
   !Output data
         if (NXUwritedata (file_id, "counts", counts, "counts") /= NX_OK) stop
            if (NXputattr (file_id, "signal", 1) /= NX_OK) stop
            if (NXputattr (file_id, "axes", "polar_angle:time_of_flight") /= NX_OK) stop
   !Close NXdata group
      if (NXclosegroup (file_id) /= NX_OK) stop
!Close NXentry group
   if (NXclosegroup (file_id) /= NX_OK) stop
!Close NeXus file
   if (NXclose (file_id) /= NX_OK) stop

end program WRITEDATA

Building Programs

The install kit provides a utility call nxbuild that can be used to build simple programs

  nxbuild -o test test.c

This script links in the various libraries for you and reading its contents would provide the necessary information for creating a separate Makefile. You can also use nxbuild with the example files in the NeXus distribution kit which are installed into /usr/local/nexus/examples - note that the executable name is important in this case as the test program uses it internally to determine the NXACC_CREATE* argument to pass to NXopen.

  nxbuild -o napi_test-hdf5 napi_test.c  #  builds HDF5 specific test

NeXus is also set up for pkg-config so the build can be done as

  gcc `pkg-config --cflags` `pkg-config --libs` -o test test.c

Reporting Bugs in the NeXus API

If you encounter any bugs in the installation or running of the NeXus API, please report them online using our IssueReporting system.