Accessing data processed with the MNMR system requires two things: An object file with a set of library functions, nmrio<machine ID>O.o, located in the $MNMR_DIR/nmrio directory, and a header file with the definitions of structures and symbols necessary, nmrio.h, located in the $MNMR_DIR/include directory.
The nmrio.h file currently contains the following basic parameters (the file may have been changed since the manual was written):
/*
Include file for nmrio
*/
/*
This code is a part of the MNMR package from
Carlsberg Research Laboratory.
(C) Copyright 1992 by Carlsberg A/S
All Rights Reserved
*/
/*
Limits for the system:
*/
#define NMR_MAX_DIM 10
#define PARSIZE 4096
#define MAGIC 431252332
struct complex
{
float re, im;
};
struct icomplex
{
long re, im;
};
/*
Format of Felix file.
*/
#define FPARSIZE 16384
#define FLSIZE 2048
struct felixpar
{
int ndim;/* 0 */
int junk1[4];/* 1 */
int compressed;/* 5 */
int junk2[14];/* 6 */
int parstart;/* 20 */
};
struct nmrbuf
{
int used;
int nused; /* no. of times accessed */
int modified; /* data modified */
int time; /* timestamp */
int fixed; /* only manual manipulation */
float **rbuf;
int cursmxindex[NMR_MAX_DIM]; /* index for first smx */
};
struct NMRFILE
{
int mnmr_magic;
int handle;
int filetype; /* 0: carlsberg, 1: Bruker, 2: Old Carlsberg, 3: Felix, 4: VNMR, 5: NMRI */
int datatype; /* 0: long, 1: float, 2: icomplex, 3: complex */
/*
These are tables with pointers to the submatrices necessary
to hold in memory:
*/
int offset, curbuf;
int nbuf;
int timer;
struct nmrbuf *buftable;
int nmr_i_dim; /* 0: reading fast, 1: reading slow etc. */
int revbytes;
int pars_modified; /* parameters modified */
int elesize, elesize2; /* no. of bytes/words per point */
int smxsize; /* size, in bytes, of each submatrix */
/*
Parameters:
*/
char comment[80];
int nmr_n_dim; /* no. of dimensions. */
int compressed;
float scalefactor;
double dmin, dmax;
int ndim[NMR_MAX_DIM]; /* no. of points in this dimens. */
int nalloc[NMR_MAX_DIM]; /* max. no. of points in this dim. */
float spec_freq[NMR_MAX_DIM]; /* spectrometer frequency, MHz */
float sweep[NMR_MAX_DIM]; /* Sweep width */
float wstep[NMR_MAX_DIM]; /* ppm per point */
float woffset[NMR_MAX_DIM]; /* w of first point */
float phase0[NMR_MAX_DIM]; /* constant phase */
float phase1[NMR_MAX_DIM]; /* linear phase */
int xdim[NMR_MAX_DIM]; /* no. of points in submatrix */
int nxdim[NMR_MAX_DIM]; /* no. of submatrices in this dim. */
int fftsize[NMR_MAX_DIM];/* no. of points for FFT */
int extract1[NMR_MAX_DIM];/* low bound for extract (starts at 0) */
int extract2[NMR_MAX_DIM];/* high bound for extract (starts at 0) */
int nwindow[NMR_MAX_DIM]; /* use window in this dimension */
int nwindow1[NMR_MAX_DIM]; /* low limit of window (starts at 0) */
int nwindow2[NMR_MAX_DIM]; /* high limit of window */
char name[16], disk[16], user[16];
int expno, procno;
int felix_compressed;/* non-zero, if reading compressed FELIX file */
int *felix_smxtable;/* Table of start addresses of sub matrices */
float *felix_tmp;/* Tmp storage of data read in... */
};
/*
Macros to access fields of structure:
*/
#define NMR_N_DIM(s) ((s)->nmr_n_dim)
#define NMR_I_DIM(s) ((s)->nmr_i_dim)
#define NDIM(s,i) ((s)->ndim[i])
#define NALLOC(s,i) ((s)->nalloc[i])
#define SPEC_FREQ(s,i) ((s)->spec_freq[i])
#define SWEEP(s,i) ((s)->sweep[i])
#define WSTEP(s,i) ((s)->wstep[i])
#define WOFFSET(s,i) ((s)->woffset[i])
#define PHASE0(s,i) ((s)->phase0[i])
#define PHASE1(s,i) ((s)->phase1[i])
#define XDIM(s,i) ((s)->xdim[i])
#define NXDIM(s,i) ((s)->nxdim[i])
#define NWINDOW(s,i) ((s)->nwindow[i])
#define NWINDOW1(s,i) ((s)->nwindow1[i])
#define NWINDOW2(s,i) ((s)->nwindow2[i])
#define INDEX(s,i,j) (((s)->woffset[i]-(j))/((s)->wstep[i]))
#define WVALUE(s,i,j) ((s)->woffset[i]-(j)*((s)->wstep[i]))
#define FFTSIZE(s,i) ((s)->fftsize[i])
#define EXTRACT1(s,i) ((s)->extract1[i])
#define EXTRACT2(s,i) ((s)->extract2[i])
Most of the statements in this header file contain information local to the nmrio.c functions. However, a knowledge of some of the fields is necessary to be able to use the NMRIO interface.
When an NMR file is opened, a handler is returned. This handler is a pointer to an instance of a structure of type NMRFILE. Several files can be opened simultaneously, as all the information for accessing the file is contained within the NMRFILE structure. The NMRFILE structure is stored in the beginning of the data file. If a new file is created, a new NMRFILE structure is created and filled with default values. When the NMR file is closed, the structure is stored in the beginning of the file. If a previously created NMR file is opened, the start of the file is simply read into the structure. The fields containing buffer administration and the file handle are set to the new values. If modifications are done to the parameters (e.g. changing ppm axes or phase correction values), the structure will only be written back to the file, if the pars_modified field is set to non-zero before the NMR file is closed.
Most of the important fields of the structure can be accessed most easily with a macro.
The important parameters are explained in the following:
Macro:
NMR_I_DIM(pnmr)
The number of the dimension currently being accessed.
Set this parameter to a non-zero value if the NMRFILE structure should be written back into the NMR file.
Size, in bytes, of each submatrix.
Macro:
NMR_N_DIM(pnmr)
The number of the dimensions in the data file.
Contains a non-zero value if the data set contains only the real part of the complex points.
Scale factor for the data in the file. This is default set to 1. If NORMALIZE has been used during the last FFT, this value will (normally) be different from 1. All data read from the file should be multiplied by this value before being used, or you should divide contouring levels etc. by this value before comparing them to the data values.
Contains the current minimum and maximum value occurring in the data file.
Macro:
NDIM(pnmr, dim)
Contains the number of points currently in dimension dim.
Macro:
NALLOC(pnmr, dim)
Contains the maximum number of points in dimension dim. These values are set when the file is created.
Macro:
SPEC_FREQ(pnmr, dim)
Contains the spectrometer frequency, in MHz, for dimension dim.
Macro:
SWEEP(pnmr, dim)
Contains the sweep width, in Hz, for dimension dim.
Macro:
WSTEP(pnmr, dim)
Contains the decrement of the ppm value for each successive points in dimension dim.
Macro:
WOFFSET(pnmr, dim)
Contains the ppm value of the first point in dimension dim. The ppm value of point number i in dimension dim is thus:
WOFFSET(pnmr, dim) - ((float) i)*WSTEP(pnmr, dim)
Macro:
PHASE0(pnmr, dim)
Contains the constant phase correction currently applied to dimension dim.
Macro:
PHASE1(pnmr, dim)
Contains the linear phase correction currently applied to dimension dim.
Macro:
XDIM(pnmr, dim)
Contains the size of the submatrix in dimension dim. The smxsize variable is thus calculated as the product of all the XDIM values with the size of each element in the submatrix (8 if the values are complex, otherwise 4).
Macro:
NXDIM(pnmr, dim)
Contains the number of submatrices along dimension dim. This will normally contain the value: NDIM(pnmr, dim)/XDIM(pnmr, dim).
Macro:
FFTSIZE(pnmr, dim)
Contains the size of the slices in dimension dim during the FFT. This can be greater than NDIM if EXTRACT has been used during FFT.
Macro:
EXTRACT1(pnmr, dim)
Contains the number of the first point extracted from the slice after the FFT in dimension dim. Points are numbered from zero.
Macro:
EXTRACT2(pnmr, dim)
Contains the number of the last point extracted from the slice after the FFT in dimension dim. Points are numbered from zero. If no EXTRACT has been performed, extract1 will be zero, and extract2 will be fftsize-1.
The following is a list of the functions available in the NMRIO package.
Formal declaration:
struct NMRFILE *nmrio_create(name, disk, user, expno, procno,
ndim, alloc, smxsizes)
char *name, *disk, *user;
int expno, procno;
int ndim, alloc[], *smxsizes;
This function creates a new data file, creates and initializes a NMRFILE structure, and returns a pointer to that structure. The first five parameters define the name of the file to be created (expno and procno are not used). Ndim is the numbers of dimensions in the data set. This number must be greater than zero, and less than or equal to NMR_MAX_DIM. Alloc and smxsizes are arrays, each with at least ndim elements. Alloc contains the desired maximum size in each dimension, alloc[0] the size for dimension 0, alloc[1] the size for dimension 1, etc. Smxsizes contains the desired sizes of the submatrices for each dimension. Element [i] in alloc should be a multiple of the same element in smxsizes, else disk space will be wasted, however, it is not an error. If a NULL pointer is passed as smxsizes, the routine will calculate the submatrix sizes. The calculated sizes can be accessed by the XDIM macro in the NMRFILE structure returned.
Note, this function is not declared in the nmrio.h file, so this must be done manually in the program, e.g.:
struct NMRFILE *nmrio_create();
The file is created to hold complex numbers. To see how to create an NMRIO file containing only real data points, see the source code for the MPROJ or MEXTRACT programs.
Format declaration:
struct NMRFILE *nmrio_open(name, disk, user, expno, procno, mode) char *name, *disk, *user; int expno, procno; int mode;
This function opens an already existing file. The first five parameters define the name of the file to be opened. The mode parameter defines the mode in which the file is to be opened: Specify O_RDONLY if the file is to be read only, or O_RDWR if the file is to be read and modified. These symbols are defined in the header file: <sys/fcntl.h>. An NMRFILE structure instance is created, and the parameters are read in from the beginning of the file. A pointer to that structure is returned by the function. If the file opened is not in NMRIO format, but in either UXNMR (2D or 3D) or FELIX format, the parameters from these file types will be converted into NMRIO format and stored in the NMRFILE structure. Note, in this case you cannot modify the parameters as they are not written back into the original format when the file is closed, even when pars_modified is set. You can read and write slices for all types of spectrum files.
Note, this function is not declared in the nmrio.h file, so this must be done manually in the program, e.g.:
struct NMRFILE *nmrio_open();
Formal declaration:
nmrio_close(pnmr) struct NMRFILE *pnmr;
This function closes an NMR file previously opened with nmrio_create or nmrio_open. The parameter passed to the nmrio_close function must be a pointer to an NMRFILE structure returned by either nmrio_create or nmrio_open. If you forget to call this function for an NMR file where either the parameters were modified or slices were modified, the last changes will be lost.
Formal declaration:
nmrio_setbuffers(pnmr, nbuf) struct NMRFILE *pnmr; int nbuf;
Defines the number of bars of submatrices to keep in memory at a time. The default value (if nmrio_setbuffers is not called) is one. See a later discussion for when to use a higher value of nbuf.
Formal declaration:
nmrio_fixbuffer(pnmr, no, index) struct NMRFILE *pnmr; int no; int *index;
This routine is normally not used. It is intended to be used, if the buffer logic contained in the NMRIO system is inadequate. If reading or writing is done at multiple places in the data file, the buffer system may become confused, and a lot of unnecessary I/O is performed. The parameter no is the number of the buffer to be fixed. Buffer numbers start from zero. The index parameter contains the index to a slice in the data set. The buffer number no will the be filled with the submatrices necessary for reading this slice. The buffer will be released (and written back, if modified), if: A: The file is closed, B: nmrio_fixbuffer is called with the same value for no, or C: A slice not contained in any buffer is asked for, and all buffers are assigned as fixed.
Formal declaration:
nmrio_setdirection(pnmr, dir) struct NMRFILE *pnmr; int dir;
This function must be called after nmrio_setbuffers, but before the first slice is read. This function defines the dimension in which to read slices. If dir is specified as zero, slices will be read in the fastest direction. The value of dir can thus be in the range 0 to NMR_N_DIM(pnmr)-1.
Formal declaration:
nmrio_set_index(pnmr, index) struct NMRFILE *pnmr; int index[];
This function must be used before any read or write operations to tell the NMRIO package what slice to read or write. The index array must at least contain NMR_N_DIM(pnmr) elements. The value of index[0] is the index in dimension zero, index[1] the value in dimension one, etc. The index value in the current reading dimension (index[NMR_I_DIM(pnmr)]) is ignored. The function will automatically calculate the necessary submatrices to read in.
Formal declaration:
nmrio_setwindow(pnmr, window1, window2) struct NMRFILE *pnmr; int window1[], window2[];
This function is called, if only a part of the data set is to be analyzed. The parameters: window1 and window2 must contain a set of index values for the lower and upper bounds of the area of the spectrum to be analyzed. If a window is set in the reading direction, a full slice is still returned by the nmrio_read_... routines, however, only the data points between window1[NMR_I_DIM(pnmr)] and window2[NMR_I_DIM(pnmr)] will be valid. The only values used in the window parameters are the values for the reading direction (which limits the number of the submatrices to read in from a bar), and the values in the slowest direction (which limits the amount to read in from each submatrix).
The program contains three functions for reading in the current slices, selected with the nmrio_set_index function. The formal declarations are:
nmrio_read_real_slice(pnmr, slice) struct NMRFILE *pnmr; float *slice; nmrio_read_imag_slice(pnmr, slice) struct NMRFILE *pnmr; float *slice; nmrio_read_slice(pnmr, slice) struct NMRFILE *pnmr; struct complex *slice;
The slice parameter must in all three cases point to an array, either of floats or complex numbers, long enough to hold a whole slice. nmrio_read_real_slice reads in only the real part of the numbers. This can be used for all types of files. The two other functions can only be used, if the file contains data with complex points. If you only want to process the real part of the numbers, use nmrio_read_real_slice! If nmrio_read_imag_slice or nmrio_read_slice is called for a data set not containing complex data points, the program will exit immediately.
Three functions exist for writing slice exist. Their formal declarations are:
nmrio_write_real_slice(pnmr, slice) struct NMRFILE *pnmr; float *slice; nmrio_write_imag_slice(pnmr, slice) struct NMRFILE *pnmr; float *slice; nmrio_write_slice(pnmr, slice) struct NMRFILE *pnmr; struct complex *slice;
The slice parameter must in all three cases point to an array containing the whole slice to be written. If you write only real values to a file created with complex data points (the default), the values of the corresponding imaginary parts will be undefined.
Before writing code to access data in an NMRIO data file, it is important to consider the order in which the slices are processed.
Figure 2The simplest case is when slices are processed one at a time, i.e. you never need to hold two or more slices in memory at a time. It is then important to access all slices in a bar of submatrices, see Figure 2, before proceeding to the next bar of submatrices. Consider the case where slices in dimension idim are processed one at a time. We do this in two loops, the outer loop counts submatrices, and the inner loop counts slices inside that bar of submatrices. A simple layout of code for doing this is as follows.
int sindex[NMR_MAX_DIM], index[NMR_MAX_DIM];
float *dataptr;
........
file1 = nmrio_open (........);
nmrio_setbuffers(file1, 1);
nmrio_setdirection(file1, idim);
dataptr = (float *) malloc(sizeof(*dataptr)*NDIM(file1, idim));
for(i=0; i<NMR_N_DIM(file1); i++) sindex[i]=0;
for(;;)
{
for(i=0; i<NMR_N_DIM(file1); i++)
index[i]=sindex[i]*XDIM(file1, i);
for(;;)
{
nmrio_set_index(file1, index);
nmrio_read_real_slice(file1, dataptr);
/* do something to dataptr! */
nmrio_set_index(file1, index);
nmrio_write_real_slice(file1, dataptr);
/*
Next slice in this bar:
*/
i=0;
if(NMR_I_DIM(file1) == 0) i=1;
for(;;)
{
index[i]++;
if(index[i]%XDIM(file1,i) != 0) break;
index[i]-=XDIM(file1, i);
i++;
if(i==NMR_I_DIM(file1)) i++;
if(i>=NMR_N_DIM(file1)) goto exit2;
}
}
exit2:
/*
Next bar of submatrices:
*/
i=0;
if(NMR_I_DIM(file1) == 0) i=1;
for(;;)
{
sindex[i]++;
if(sindex[i]<NXDIM(file1, i)) break;
sindex[i]=0;
i++;
if(i==NMR_I_DIM(file1)) i++;
if(i>=NMR_N_DIM(file1)) goto exit1;
}
}
exit1:
nmrio_close(file1);
Figure 3If your program needs to access one whole plane of data points at a time, a different approach has to be taken. Consider the case, where you want to read planes in the dimensions (idim, jdim). In Figure 3, idim is 0 and jdim is 2 (or idim is 2 and jdim is 0). In order to speed up operations, the NMRIO software should be set up with more than one buffer. This is demonstrated in the code layout below (this will only work for a 3D data set, but could easily be extended to higher dimensions):
int index[NMR_MAX_DIM];
float *dataptr;
........
file1 = nmrio_open (........);
nmrio_setbuffers(file1, NXDIM(file1, jdim));
nmrio_setdirection(file1, idim);
dataptr = (float *) malloc(sizeof(*dataptr)*
NDIM(file1, idim)*NDIM(file1, jdim));
kdim = 3-idim-jdim;
for(planeno=0; planeno<NDIM(file1, kdim); planeno++)
{
/*
For each plane:
/*
for(j=0; j<NDIM(file1, jdim); j++)
{
index[idim] = 0; /* not necessary */
index[jdim] = j;
index[kdim] = planeno;
nmrio_set_index(file1, index);
nmrio_read_real_slice(file1, dataptr+j*NDIM(file1, idim));
}
/* dataptr now contains whole plane number planeno! */
}
nmrio_close(file1);
Note, that this way of accessing the data requires more space for buffers.
Figure 4If a box of numbers is extracted from a 3D data set, this box could in fact contain numbers from four different bars of submatrices, see Figure 4. If the size of one of the sides of the box is greater than the size of the submatrix in that dimension, more than four bars could be accessed. If the box is very small, e.g. 3 x 3 x 3 numbers, this is probably not very likely to occur. In this case you would set up NMRIO to use 4 sets of buffers. If you only use one buffer, or if the side length of the box is increased above the size of the submatrix, this will not make the program crash, it will just make it run more slowly. The following is a short example of this.
#define IBOX 3
#define JBOX 3
#define KBOX 3
int index[NMR_MAX_DIM];
float *dataptr;
float box[IBOX][JBOX][KBOX];
........
file1 = nmrio_open (........);
nmrio_setbuffers(file1, 4);
nmrio_setdirection(file1, idim);
dataptr = (float *) malloc(sizeof(*dataptr)*
NDIM(file1, idim)*JBOX*KBOX);
kdim = 3-idim-jdim;
for(j=0; j<NDIM(file1, jdim)-JBOX+1; j++)
{
for(k=0; j<NDIM(file1, kdim)-KBOX+1; k++)
{
for(j1=0; j1<JBOX; j1++)
{
for(k1=0; k1<KBOX; k1++)
{
index[idim] = 0; /* not necessary */
index[jdim] = j+j1;
index[kdim] = k+k1;
nmrio_set_index(file1, index);
nmrio_read_real_slice(file1, dataptr+
(j1*KBOX+k1)*NDIM(file1, idim));
}
}
for(i=0; i<NDIM(file1, idim)-IBOX+1; i++)
{
for(i1=0; i1<IBOX; i1++)
for(j1=0; j1<JBOX; j1++)
for(k1=0; k1<KBOX; k1++)
box[i1][j1][k1] = dataptr[(j1*KBOX+k1)*NDIM(file1, idim)+i+i1];
/* box now contains box */
}
}
}
nmrio_close(file1);
To get the best performance, it is not necessary to move the data out of the dataptr array into the box array; simply perform the operations on the numbers while they are in the dataptr array, using the proper subscripts.