| 1 | <HTML> |
|---|
| 2 | <HEAD> |
|---|
| 3 | <TITLE>The NeXus for Java API Tutorial</TITLE> |
|---|
| 4 | </HEAD> |
|---|
| 5 | <BODY bgcolor=#FFFFFF> |
|---|
| 6 | <p align="center"> |
|---|
| 7 | <IMG src="NeXus.gif" WIDTH=597 HEIGHT=90> |
|---|
| 8 | </p> |
|---|
| 9 | <H1>The NeXus for Java API Tutorial</H1> |
|---|
| 10 | <P> |
|---|
| 11 | This document explains in more detail how to program with the NeXus |
|---|
| 12 | API for Java. The intended audience are Java programmers who do not |
|---|
| 13 | know the C language NeXus API. This document will only explain how to |
|---|
| 14 | deal with NeXus files. For a general description of NeXus see the <a |
|---|
| 15 | href="http://lns00.psi.ch/nexus">NeXus WWW-pages</a>. For |
|---|
| 16 | reference,see the <a href="apidoc/Package-org.nexusformat.html">jnexus API |
|---|
| 17 | documentation</a>. Another good source of information is the <a |
|---|
| 18 | href="test/TestJapi.java">test driver source code</a> for the NeXus |
|---|
| 19 | API for Java. It is a more involved example of API usage. And it is |
|---|
| 20 | documented! |
|---|
| 21 | </P> |
|---|
| 22 | <p> |
|---|
| 23 | Before doing anything with the NeXus for Java API the necessary |
|---|
| 24 | classes need to be imported. This is done with the statement: |
|---|
| 25 | <center>import org.nexusformat.*;</center> |
|---|
| 26 | at the head of your Java file. |
|---|
| 27 | A NeXus programmer has to deal with the following five concepts: |
|---|
| 28 | <dl> |
|---|
| 29 | <dt><a href="#fil">NeXus Files</a> |
|---|
| 30 | <dd>You should have guessed as much. |
|---|
| 31 | <dt><a href="#group">Groups</a> |
|---|
| 32 | <dd>Groups are NeXus means of structuring data in a file. Groups can |
|---|
| 33 | hold other groups or datsets. The filesystem analogon to groups would be |
|---|
| 34 | directories. |
|---|
| 35 | <dt><a href="#sds">SDS</a> |
|---|
| 36 | <dd>SDS are scientific datasets. This is a n-dimensional array of |
|---|
| 37 | numbers stored in the file. |
|---|
| 38 | <dt><a href="#att">Attributes</a> |
|---|
| 39 | <DD>Attributes is auxiliary information stored in the file. Two types |
|---|
| 40 | of attributes are possible: global file wide attributes and attributes |
|---|
| 41 | linked to a SDS. |
|---|
| 42 | <dt><a href="#link">Links</a> |
|---|
| 43 | <dd>For organisational reasons it might be useful to refer a SDS in more |
|---|
| 44 | then one group. But it should be avoided to duplicate data. In order |
|---|
| 45 | to avoid this it is possible to link SDS wherever you want. This |
|---|
| 46 | concept is quite similar to a symbolic link in a unix file system. |
|---|
| 47 | </dl> |
|---|
| 48 | All these concepts will be explained in more detail below. A note |
|---|
| 49 | about error handling: A NexusException is thrown whenever an error |
|---|
| 50 | occurs. This implies that all code samples stated below should be |
|---|
| 51 | included in a try-catch block looking like this: |
|---|
| 52 | <pre> |
|---|
| 53 | try{ |
|---|
| 54 | // some NeXus for Java calls. |
|---|
| 55 | }catch(NexusException ne) { |
|---|
| 56 | // analyze and treat the error |
|---|
| 57 | } |
|---|
| 58 | </pre> |
|---|
| 59 | For brevity and clarity this will be left out in the following text. |
|---|
| 60 | </p> |
|---|
| 61 | <h2><a name=fil>NeXus Files</a></h2> |
|---|
| 62 | <p> |
|---|
| 63 | A Nexus File is opened by: |
|---|
| 64 | <center>NexusFile nf = new NexusFile(name, |
|---|
| 65 | NexusFile.NXACC_CREATE);</center> |
|---|
| 66 | The first parameter to the constructor is of course the name. The |
|---|
| 67 | second parameter is the access code valid for the file: Three access |
|---|
| 68 | codes are supported:<dl> |
|---|
| 69 | <dt>NXACC_CREATE, NXACC_CREATE4 |
|---|
| 70 | <dd>For creating a new NeXus file as HDF-4 file. |
|---|
| 71 | <dt>NXACC_CREATE% |
|---|
| 72 | <dd>For creating a new NeXus file as HDF-5 file. |
|---|
| 73 | <dt>NXACC_RDWR |
|---|
| 74 | <dd>For opening an existing NeXus file for modification or for |
|---|
| 75 | appending. |
|---|
| 76 | <dt>NXACC_READ |
|---|
| 77 | <dd>Open a file for reading only. |
|---|
| 78 | </dl> |
|---|
| 79 | Please note that for all further examples, the name nf is assumed for |
|---|
| 80 | the NexusFile object. |
|---|
| 81 | </p> |
|---|
| 82 | <p> |
|---|
| 83 | Closing files is accomplished through the finalize method. This should |
|---|
| 84 | be called automatically by the Java garbage collector but it is safer |
|---|
| 85 | to explicitly call this method when done with a file. |
|---|
| 86 | <center>nf.finalize()</center> |
|---|
| 87 | does the trick. |
|---|
| 88 | </p> |
|---|
| 89 | <p> |
|---|
| 90 | Sometimes it is necessary to flush all buffered data to disk before |
|---|
| 91 | doing for instance something else in a program in order to prevent |
|---|
| 92 | data loss. This can be done with the flush method: |
|---|
| 93 | <center>nf.flush();</center> |
|---|
| 94 | flush has the side effect of closing all open SDS. |
|---|
| 95 | </p> |
|---|
| 96 | <h2><a name="group">Groups</a></h2> |
|---|
| 97 | <p> |
|---|
| 98 | A group (or vGroup) is the NeXus equivalent of a directory. Alike to a |
|---|
| 99 | directory hierarchy, a hierarchy of groups can be built in a NeXus file. In |
|---|
| 100 | contrast to directory names however, NeXus group names consist of two |
|---|
| 101 | strings: the groupname and the groupclass. Both strings are needed in |
|---|
| 102 | order to address a NeXus group. There are API functions for all |
|---|
| 103 | necessary operations on groups. The first one is group creation: |
|---|
| 104 | <center>nf.makegroup(name, nxclass);</center> |
|---|
| 105 | This corresponds to a mkdir in a unix filesystem. |
|---|
| 106 | </p> |
|---|
| 107 | <p> |
|---|
| 108 | In order to use a group we need a means of traversing the group |
|---|
| 109 | hierarchy. For this the methods: |
|---|
| 110 | <center>nf.opengroup(name,nxclass);</center> and |
|---|
| 111 | <center>nf.closegroup();</center> are provided. |
|---|
| 112 | opengroup corresponds to a cd name,class and steps into the group name |
|---|
| 113 | with class nxclass. closegroup corresponds to cd .. and steps one |
|---|
| 114 | group lower in the group hierarchy. |
|---|
| 115 | </p> |
|---|
| 116 | <p> |
|---|
| 117 | NeXus is self describing. Clearly a method is needed to find out about |
|---|
| 118 | the contents of the current group. For this the method: |
|---|
| 119 | <center>Hashtable ha = nf.groupdir();</center> |
|---|
| 120 | is used. The hashtable returned contains pairs of name, class as |
|---|
| 121 | entries. For datasets the class name is set to SDS. See the following |
|---|
| 122 | code snippet as an example how to print the contents of a NeXus group: |
|---|
| 123 | <pre> |
|---|
| 124 | Hashtable h = nf.groupdir(); |
|---|
| 125 | e = h.keys(); |
|---|
| 126 | System.out.println("Found in Group"); |
|---|
| 127 | while(e.hasMoreElements()) |
|---|
| 128 | { |
|---|
| 129 | vname = (String)e.nextElement(); |
|---|
| 130 | vclass = (String)h.get(vname); |
|---|
| 131 | System.out.println(" Item: " + vname + " class: " + vclass); |
|---|
| 132 | } |
|---|
| 133 | </pre> |
|---|
| 134 | </p> |
|---|
| 135 | <h2><a name="sds">SDS</a></h2> |
|---|
| 136 | SDS are scientific dataset. They are used to store n-dimensional |
|---|
| 137 | arrays of data in a variety of number types in a NeXus file. The |
|---|
| 138 | following number types are allowed in NeXus files: |
|---|
| 139 | <pre> |
|---|
| 140 | NexusFile.NX_INT8: |
|---|
| 141 | NexusFile.NX_UINT8: |
|---|
| 142 | NexusFile.NX_CHAR: |
|---|
| 143 | NexusFile.NX_INT16: |
|---|
| 144 | NexusFile.NX_UINT16: |
|---|
| 145 | NexusFile.NX_INT32: |
|---|
| 146 | NexusFile.NX_UINT32: |
|---|
| 147 | NexusFile.NX_FLOAT32: |
|---|
| 148 | NexusFile.NX_FLOAT64: |
|---|
| 149 | </pre> |
|---|
| 150 | I think the names are self describing. |
|---|
| 151 | These types are defined as constants in NexusFile.java. |
|---|
| 152 | <p> |
|---|
| 153 | When creating a new file a means is needed for creating a new SDS in |
|---|
| 154 | the NeXus file. A SDS is fully characterized by its name, its number |
|---|
| 155 | type (out of the list above), the number of dimensions it has (its |
|---|
| 156 | rank) and its size in each dimension. With this information a SDS can |
|---|
| 157 | be created: |
|---|
| 158 | <center>nf.makedata(name,type,rank,iDim);</center>with iDim being an |
|---|
| 159 | integer array holding the size of the dataset in each dimension. A |
|---|
| 160 | speciality of NeXus (and HDF) is that the first dimension can be |
|---|
| 161 | unlimited. Simpy set the dimension 0. Then data can be appended in |
|---|
| 162 | consecutive steps along this dimension. Please note, that makedata |
|---|
| 163 | does not automatically open the SDS. Before writing data to it, a call |
|---|
| 164 | to opendata is required. |
|---|
| 165 | <p> |
|---|
| 166 | Analog to a file in a filesystem a SDS must be opened before anything |
|---|
| 167 | can be done with it and closed when processing is finished. The |
|---|
| 168 | appropriate calls are: |
|---|
| 169 | <center>nf.opendata(name);</center> and <center>nf.closedata();</center> |
|---|
| 170 | Please |
|---|
| 171 | note that all methods below this section require an openend SDS for |
|---|
| 172 | proper operation. |
|---|
| 173 | <p> |
|---|
| 174 | Once a SDS is open data can be read or written to it. Two means of |
|---|
| 175 | data transfer are provided: putdata, getdata write and read all the |
|---|
| 176 | data in one go, whereas putslab, getslab allows to write and read |
|---|
| 177 | subsets of data. There is a trick here though. Java is meant to be |
|---|
| 178 | type safe. One |
|---|
| 179 | would think then that a data transfer method would be required for each Java |
|---|
| 180 | data type. In order to avoid this the data to transfer is passed into |
|---|
| 181 | the data transfer methods as type Object. Then the API proceeds to |
|---|
| 182 | analyze this object |
|---|
| 183 | through the Java introspection API and converts the data to a byte |
|---|
| 184 | stream for writing through the native method call. This is an elegant |
|---|
| 185 | solution with one drawback: An array is needed at all times. Even if |
|---|
| 186 | only a single data value is written (or read) an array of length one |
|---|
| 187 | and an appropriate type is the required argument. |
|---|
| 188 | <p> |
|---|
| 189 | Writing and reading then looks like: |
|---|
| 190 | <pre> |
|---|
| 191 | // example data |
|---|
| 192 | int iData[][] = new iData[3][10]; |
|---|
| 193 | // write it |
|---|
| 194 | nf.putdata(iData); |
|---|
| 195 | // read it |
|---|
| 196 | nf.getdata(iData); |
|---|
| 197 | </pre> |
|---|
| 198 | <p> |
|---|
| 199 | Another issue are strings. Strings are first class objects in |
|---|
| 200 | Java. HDF (and NeXus) sees them as dumb arrays of bytes. Thus strings |
|---|
| 201 | have to be converted to and from bytes when reading string data. See a |
|---|
| 202 | writing example: |
|---|
| 203 | <pre> |
|---|
| 204 | String ame = "Alle meine Entchen"; |
|---|
| 205 | nf.makedata("string_data",NexusFile.NX_CHAR,1, |
|---|
| 206 | ame.length()+2); |
|---|
| 207 | nf.opendata("string_data"); |
|---|
| 208 | nf.putdata(ame.getBytes()); |
|---|
| 209 | </pre> |
|---|
| 210 | And reading: |
|---|
| 211 | <pre> |
|---|
| 212 | byte bData[] = new byte[132]; |
|---|
| 213 | nf.opendata("string_data"); |
|---|
| 214 | nf.getdata(bData); |
|---|
| 215 | String string_data = new String(bData); |
|---|
| 216 | </pre> |
|---|
| 217 | The aforementioned holds for all strings written as SDS content or as |
|---|
| 218 | an attribute. SDS or vGroup names do not need this treatment. |
|---|
| 219 | <p> |
|---|
| 220 | When writing a subset of data two more arguments are needed: The first |
|---|
| 221 | is an integer array of size rank which holds the address in the |
|---|
| 222 | dataset where to start the transfer of the subset. The second is another |
|---|
| 223 | integer array of size rank which determines the size of the data |
|---|
| 224 | subset to transfer in each dimension. The methods then look like: |
|---|
| 225 | <pre> |
|---|
| 226 | // example data |
|---|
| 227 | int iData[][] = new iData[3][10]; |
|---|
| 228 | int iStart[2] = {0,0}; |
|---|
| 229 | int iSize[2] = {3,10}; |
|---|
| 230 | // write it |
|---|
| 231 | nf.putslab(iStart, iSize,iData); |
|---|
| 232 | // read it |
|---|
| 233 | nf.getdata(iStart, iSize,iData); |
|---|
| 234 | </pre> |
|---|
| 235 | This example is a bit contrieved in that it uses the subset API for |
|---|
| 236 | transfering the whole dataset. |
|---|
| 237 | <p> |
|---|
| 238 | NeXus and HDF support the compression and decompression of data on the |
|---|
| 239 | fly during transfer operations. The only thing which needs to be done |
|---|
| 240 | is to tell NeXus to compress the data before writing data. An example |
|---|
| 241 | looks like this: |
|---|
| 242 | <pre> |
|---|
| 243 | float fData[][] = new float[100][1000]; |
|---|
| 244 | int iDim[] = new int[2]; |
|---|
| 245 | iDim[0] = 100; |
|---|
| 246 | iDim[1] = 1000; |
|---|
| 247 | |
|---|
| 248 | nf.compmakedata("fData",2,NexusFile.NX_FLOAT32,iDim, |
|---|
| 249 | NexusFile.COMP_CODE_LZW,iDim); |
|---|
| 250 | nf.opendata("fData"); |
|---|
| 251 | nf.putdata(fData); |
|---|
| 252 | </pre> |
|---|
| 253 | The additional parameters to NXcompmakedata are the compression type |
|---|
| 254 | and a buffer size for use used during compression. This is an integer |
|---|
| 255 | array of the same rank as the data. If the data set is written |
|---|
| 256 | in one go this should be the dimensions of the dataset, |
|---|
| 257 | when writing in slabs, the slab size. This is a performance feauture. |
|---|
| 258 | |
|---|
| 259 | Please note the sequence of calls. The parameter to compress is the |
|---|
| 260 | compression algorithm to use. Permitted values are: |
|---|
| 261 | <dl> |
|---|
| 262 | <dt>NexusFile.NX_COMP_NONE |
|---|
| 263 | <DD> No compression |
|---|
| 264 | <dt>NexusFile.NX_COMP_RLE |
|---|
| 265 | <DD> Run length encoding |
|---|
| 266 | <dt>NexusFile.NX_COMP_LZW |
|---|
| 267 | <DD> gzip type compression |
|---|
| 268 | <dt>NexusFile.NX_COMP_HUF |
|---|
| 269 | <DD> Huffman compression. |
|---|
| 270 | </dl> |
|---|
| 271 | Please note that transfers to compressed datasets have to be done |
|---|
| 272 | through the putdata, getdata routines, subset operations are not |
|---|
| 273 | supported with compression. When using HDF-5 as underlying file format, only LZW compression is available. |
|---|
| 274 | <p> |
|---|
| 275 | When dealing with an unknown NeXus file we might need to find out |
|---|
| 276 | about the characteristics of a SDS. This can be done with: |
|---|
| 277 | <center>nf.getinfo(iDim,args);</center> |
|---|
| 278 | After this call iDim will hold the size of the SDS in each dimension, |
|---|
| 279 | args[0] will be the rank of the SDS and args[1] the number type. Make |
|---|
| 280 | sure that iDim is large enough to hold all dimensions. Hint: 32 is the |
|---|
| 281 | maximum number of dimensions supported by HDF. |
|---|
| 282 | <p> |
|---|
| 283 | <h2><a name="att">Attributes</a></h2> |
|---|
| 284 | Attributes are auxiliary information stored in a NeXus file. There are |
|---|
| 285 | two variants: global attributes at file level and attributes at SDS |
|---|
| 286 | level. The attribute part of the API acts on global attributes if no |
|---|
| 287 | SDS is open and on SDS attributes if an SDS has been opened with |
|---|
| 288 | opendata(). Attributes can be written: |
|---|
| 289 | <center>nf.putattr(name,data,type);</center> |
|---|
| 290 | name is a name, data is a one dimensional array of some data and type |
|---|
| 291 | is the of the data. The same data types as for SDS writing are |
|---|
| 292 | supported. |
|---|
| 293 | <p> |
|---|
| 294 | Attributes can be read: |
|---|
| 295 | <center>nf.getattr(name, data, args);</center> |
|---|
| 296 | args[0] will hold the length of the attribute array, args[1] its type. |
|---|
| 297 | This must be supplied as input. Proper values for these parameters can |
|---|
| 298 | be inquired trough the attribute directory method: |
|---|
| 299 | <center>Hashtable ha = nf.attrdir();</center> |
|---|
| 300 | This time the hashtable ha will hold pairs of attribute names and |
|---|
| 301 | AttributeEntry objects. These objects are small classes which hold the |
|---|
| 302 | length and type of the attribute. See an attribute printing example |
|---|
| 303 | for more information: |
|---|
| 304 | <pre> |
|---|
| 305 | AttributeEntry atten; |
|---|
| 306 | String attname; |
|---|
| 307 | |
|---|
| 308 | Hashtable h = nf.attrdir(); |
|---|
| 309 | Enumeration e = h.keys(); |
|---|
| 310 | while(e.hasMoreElements()) |
|---|
| 311 | { |
|---|
| 312 | attname = (String)e.nextElement(); |
|---|
| 313 | atten = (AttributeEntry)h.get(attname); |
|---|
| 314 | System.out.println("Found global attribute: " + attname + |
|---|
| 315 | " type: "+ atten.type + " ,length: " + atten.length); |
|---|
| 316 | } |
|---|
| 317 | </pre> |
|---|
| 318 | <h2><a name="link">Linking</a></h2> |
|---|
| 319 | Linking a SDS into more then one group requires some |
|---|
| 320 | precautions. First some internal information needed for linking must |
|---|
| 321 | be retrieved while the SDS ist still open. The call: |
|---|
| 322 | <center>NXlink nl = nf.getdataID();</center> does just that. Then, |
|---|
| 323 | after moving to the appropriate place for the link in the |
|---|
| 324 | group hierarchy the call: |
|---|
| 325 | <center>nf.makelink(nl);</center> |
|---|
| 326 | will actually install the link. |
|---|
| 327 | <p> |
|---|
| 328 | </BODY> |
|---|
| 329 | </HTML> |
|---|