This document explains in more detail how to program with the NeXus API for Java. The intended audience are Java programmers who do not know the C language NeXus API. This document will only explain how to deal with NeXus files. For a general description of NeXus see the NeXus WWW-pages. For reference,see the jnexus API documentation. Another good source of information is the test driver source code for the NeXus API for Java. It is a more involved example of API usage. And it is documented!
Before doing anything with the NeXus for Java API the necessary classes need to be imported. This is done with the statement:
try{ // some NeXus for Java calls. }catch(NexusException ne) { // analyze and treat the error }For brevity and clarity this will be left out in the following text.
A Nexus File is opened by:
Closing files is accomplished through the finalize method. This should be called automatically by the Java garbage collector but it is safer to explicitly call this method when done with a file.
Sometimes it is necessary to flush all buffered data to disk before doing for instance something else in a program in order to prevent data loss. This can be done with the flush method:
A group (or vGroup) is the NeXus equivalent of a directory. Alike to a directory hierarchy, a hierarchy of groups can be built in a NeXus file. In contrast to directory names however, NeXus group names consist of two strings: the groupname and the groupclass. Both strings are needed in order to address a NeXus group. There are API functions for all necessary operations on groups. The first one is group creation:
In order to use a group we need a means of traversing the group hierarchy. For this the methods:
NeXus is self describing. Clearly a method is needed to find out about the contents of the current group. For this the method:
Hashtable h = nf.groupdir(); e = h.keys(); System.out.println("Found in Group"); while(e.hasMoreElements()) { vname = (String)e.nextElement(); vclass = (String)h.get(vname); System.out.println(" Item: " + vname + " class: " + vclass); }
NexusFile.NX_INT8: NexusFile.NX_UINT8: NexusFile.NX_CHAR: NexusFile.NX_INT16: NexusFile.NX_UINT16: NexusFile.NX_INT32: NexusFile.NX_UINT32: NexusFile.NX_FLOAT32: NexusFile.NX_FLOAT64:I think the names are self describing. These types are defined as constants in NexusFile.java.
When creating a new file a means is needed for creating a new SDS in the NeXus file. A SDS is fully characterized by its name, its number type (out of the list above), the number of dimensions it has (its rank) and its size in each dimension. With this information a SDS can be created:
Analog to a file in a filesystem a SDS must be opened before anything can be done with it and closed when processing is finished. The appropriate calls are:
Once a SDS is open data can be read or written to it. Two means of data transfer are provided: putdata, getdata write and read all the data in one go, whereas putslab, getslab allows to write and read subsets of data. There is a trick here though. Java is meant to be type safe. One would think then that a data transfer method would be required for each Java data type. In order to avoid this the data to transfer is passed into the data transfer methods as type Object. Then the API proceeds to analyze this object through the Java introspection API and converts the data to a byte stream for writing through the native method call. This is an elegant solution with one drawback: An array is needed at all times. Even if only a single data value is written (or read) an array of length one and an appropriate type is the required argument.
Writing and reading then looks like:
// example data int iData[][] = new iData[3][10]; // write it nf.putdata(iData); // read it nf.getdata(iData);
Another issue are strings. Strings are first class objects in Java. HDF (and NeXus) sees them as dumb arrays of bytes. Thus strings have to be converted to and from bytes when reading string data. See a writing example:
String ame = "Alle meine Entchen"; nf.makedata("string_data",NexusFile.NX_CHAR,1, ame.length()+2); nf.opendata("string_data"); nf.putdata(ame.getBytes());And reading:
byte bData[] = new byte[132]; nf.opendata("string_data"); nf.getdata(bData); String string_data = new String(bData);The aforementioned holds for all strings written as SDS content or as an attribute. SDS or vGroup names do not need this treatment.
When writing a subset of data two more arguments are needed: The first is an integer array of size rank which holds the address in the dataset where to start the transfer of the subset. The second is another integer array of size rank which determines the size of the data subset to transfer in each dimension. The methods then look like:
// example data int iData[][] = new iData[3][10]; int iStart[2] = {0,0}; int iSize[2] = {3,10}; // write it nf.putslab(iStart, iSize,iData); // read it nf.getdata(iStart, iSize,iData);This example is a bit contrieved in that it uses the subset API for transfering the whole dataset.
NeXus and HDF support the compression and decompression of data on the fly during transfer operations. The only thing which needs to be done is to tell NeXus to compress the data before writing data. An example looks like this:
float fData[][] = new float[100][1000]; int iDim[] = new int[2]; iDim[0] = 100; iDim[1] = 1000; nf.makedata("fData",2,NexusFile.NX_FLOAT32,iDim); nf.opendata("fData"); nf.compress(NexusFile.COMP_CODE_LZW); nf.putdata(fData);Please note the sequence of calls. The parameter to compress is the compression algorithm to use. Permitted values are:
When dealing with an unknown NeXus file we might need to find out about the characteristics of a SDS. This can be done with:
Attributes can be read:
AttributeEntry atten; String attname; Hashtable h = nf.attrdir(); Enumeration e = h.keys(); while(e.hasMoreElements()) { attname = (String)e.nextElement(); atten = (AttributeEntry)h.get(attname); System.out.println("Found global attribute: " + attname + " type: "+ atten.type + " ,length: " + atten.length); }