Package reflectometry :: Package reduction :: Module nxs

Source Code for Module reflectometry.reduction.nxs

   1  # This program is public domain 
   2   
   3  # Author: Paul Kienzle 
   4   
   5  """ 
   6  Wrapper for the NeXus shared library. 
   7   
   8   
   9  Library Location 
  10  ================ 
  11   
  12  This wrapper needs the location of the libNeXus precompiled binary. It 
  13  looks in the following places in order: 
  14      os.environ['NEXUSLIB']                  - All 
  15      directory containing nxs.py             - All 
  16      os.environ['NEXUSDIR']\bin              - Windows 
  17      os.environ['LD_LIBRARY_PATH']           - Unix 
  18      os.environ['DYLD_LIBRARY_PATH']         - Darwin 
  19      PREFIX/lib                              - Unix and Darwin 
  20      /usr/local/lib                          - Unix and Darwin 
  21      /usr/lib                                - Unix and Darwin 
  22   
  23  On Windows it looks for libNeXus.dll and libNeXus-0.dll; 
  24  NEXUSDIR defaults to r'C:\Program Files\NeXus Data Format' 
  25  On OS X it looks for libNeXus.dylib 
  26  On Unix it looks for libNeXus.so 
  27  PREFIX defaults to /usr/local, but is replaced by the value of 
  28  --prefix during configure. 
  29   
  30  The import will raise an OSError exception if the library wasn't found 
  31  or couldn't be loaded.  Note that on Windows in particular this may be 
  32  because the supporting HDF5 dlls were not available in the usual places. 
  33   
  34  If you are extracting the nexus library from a bundle at runtime, set 
  35  os.environ['NEXUSLIB'] to the path where it is extracted before the 
  36  first import of nxs. 
  37   
  38  Interface 
  39  ========= 
  40   
  41  Full documentation of the NeXus API is available at nexusformat.org. 
  42   
  43  This wrapper differs from napi in several respects: 
  44    - Data values are loaded/stored directly from numpy arrays. 
  45    - Return codes are turned into exceptions. 
  46    - The file handle is stored in a file object 
  47    - Constants are handled somewhat differently (see below) 
  48    - Type checking on data/parameter storage 
  49    - Adds iterators file.entries() and file.attrs() 
  50    - Adds link() function to return the name of the linked to group, if any 
  51    - NXmalloc/NXfree are not needed. 
  52   
  53  Example: 
  54   
  55    import nxs 
  56    file = nxs.open('filename.nxs','rw') 
  57    file.opengroup('entry1') 
  58    file.opendata('definition') 
  59    print file.getdata() 
  60    file.close() 
  61   
  62    See nxstest.py for a more complete example. 
  63   
  64  File open modes can be constants or strings: 
  65   
  66    nxs.ACC_READ      'r' 
  67    nxs.ACC_RDWR      'rw' 
  68    nxs.ACC_CREATE    'w' 
  69    nxs.ACC_CREATE4   'w4' 
  70    nxs.ACC_CREATE5   'w5' 
  71    nxs.ACC_CREATEXML 'wx' 
  72   
  73  Dimension constants: 
  74   
  75    nxs.UNLIMITED  - for the extensible data dimension 
  76    nxs.MAXRANK    - for the number of possible dimensions 
  77   
  78  Data types are strings corresponding to the numpy data types: 
  79   
  80    'float32' 'float64' 
  81    'int8' 'int16' 'int32' 'int64' 
  82    'uint8' 'uint16' 'uint32' 'uint64' 
  83   
  84    Use 'char' for strings.  You can use the numpy dtype attribute for the 
  85    data type. 
  86   
  87  Dimensions are lists of integers or numpy arrays.  You can use the 
  88  numpy shape attribute for the dimensions. 
  89   
  90  Compression codes are: 
  91   
  92   'none' 'lzw' 'rle' 'huffman' 
  93   
  94    As of this writing NeXus only supports 'none' and 'lzw'. 
  95   
  96  Miscellaneous constants: 
  97   
  98    nxs.MAXNAMELEN  - names must be shorter than this 
  99    nxs.MAXPATHLEN  - total path length must be shorter than this 
 100   
 101   
 102  Caveats 
 103  ======= 
 104   
 105  TODO: NOSTRIP constant is probably not handled properly, 
 106  TODO: Embedded nulls in strings is not supported 
 107   
 108  WARNING:  We have a memory leak.  Calling open/close costs about 90k a pair. 
 109  This is an eigenbug: 
 110     - if I test ctypes on a simple library it does not leak 
 111     - if I use the leak_test1 code in the nexus distribution it doesn't leak 
 112     - if I remove the open/close call in the wrapper it doesn't leak. 
 113   
 114  """ 
 115  import sys, os, numpy, ctypes 
 116   
 117  # Defined ctypes 
 118  from ctypes import c_void_p, c_int, c_long, c_char, c_char_p 
 119  from ctypes import byref as _ref 
 120  c_void_pp = ctypes.POINTER(c_void_p) 
 121  c_int_p = ctypes.POINTER(c_int) 
 128  c_NXlink_p = ctypes.POINTER(_NXlink) 
 129   
 130   
 131  # Open modes: 
 132  ACC_READ,ACC_RDWR,ACC_CREATE=1,2,3 
 133  ACC_CREATE4,ACC_CREATE5,ACC_CREATEXML=4,5,6 
 134  _nxopen_mode=dict(r=1,rw=2,w=3,w4=4,w5=5,wx=6) 
 135  NOSTRIP=128 
 136   
 137  # Status codes 
 138  OK,ERROR,EOD=1,0,-1 
 139   
 140  # Other constants 
 141  UNLIMITED=-1 
 142  MAXRANK=32 
 143  MAXNAMELEN=64 
 144  MAXPATHLEN=1024 # inferred from code 
 145   
 146  # HDF data types from numpy types 
 147  _nxtype_code=dict( 
 148      char=4, 
 149      float32=5,float64=6, 
 150      int8=20,uint8=21, 
 151      int16=22,uint16=23, 
 152      int32=24,uint32=25, 
 153      int64=26,uint64=27, 
 154      ) 
 155  # Python types from HDF data types 
 156  # Other than 'char' for the string type, the python types correspond to 
 157  # the numpy data types, and can be used directly to create numpy arrays. 
 158  # Note: put this in a lambda to hide v,k from the local namespace 
 159  _pytype_code=(lambda : dict([(v,k) for (k,v) in _nxtype_code.iteritems()]))() 
 160   
 161  # Compression to use when creating data blocks 
 162  _compression_code=dict( 
 163      none=100, 
 164      lzw=200, 
 165      rle=300, 
 166      huffman=400) 
 167   
168 -def _is_string_like(obj):
169 """ 170 Return True if object acts like a string. 171 """ 172 # From matplotlib cbook.py John D. Hunter 173 # Python 2.2 style licence. See license.py in matplotlib for details. 174 if hasattr(obj, 'shape'): return False 175 try: obj + '' 176 except (TypeError, ValueError): return False 177 return True
178
179 -def _is_list_like(obj):
180 """ 181 Return True if object acts like a list 182 """ 183 try: obj + [] 184 except TypeError: return False 185 return True
186
187 -def _libnexus():
188 """ 189 Load the NeXus library whereever it may be. 190 """ 191 # this will get changed as part of the install process 192 # it should correspond to --prefix specified to ./configure 193 nxprefix = '/usr/local' 194 # NEXUSLIB takes precedence 195 if 'NEXUSLIB' in os.environ: 196 file = os.environ['NEXUSLIB'] 197 if not os.path.isfile(file): 198 raise OSError, \ 199 "File %s from environment variable NEXUSLIB does exist"%(file) 200 files = [file] 201 else: 202 files = [] 203 204 # Default names and locations to look for the library are system dependent 205 filedir = os.path.dirname(__file__) 206 if sys.platform in ('win32','cygwin'): 207 # NEXUSDIR is set by the Windows installer for NeXus 208 if 'NEXUSDIR' in os.environ: 209 winnxdir = os.environ['NEXUSDIR'] 210 else: 211 winnxdir = 'C:/Program Files/NeXus Data Format' 212 213 files += [filedir+"/libNeXus.dll", 214 filedir+"/libNeXus-0.dll", 215 winnxdir + '/bin/libNeXus-0.dll'] 216 else: 217 if sys.platform in ('darwin'): 218 lib = 'libNeXus.dylib' 219 ldenv = 'DYLD_LIBRARY_PATH' 220 else: 221 lib = 'libNeXus.so' 222 ldenv = 'LD_LIBRARY_PATH' 223 # Search the load library path as well as the standard locations 224 ldpath = [p for p in os.environ.get(ldenv,'').split(':') if p != ''] 225 stdpath = [ nxprefix+'/lib', '/usr/local/lib', '/usr/lib'] 226 files += [os.path.join(p,lib) for p in [filedir]+ldpath+stdpath] 227 228 # Given a list of files, try loading the first one that is available. 229 for file in files: 230 if not os.path.isfile(file): continue 231 try: 232 return ctypes.cdll[file] 233 except: 234 raise OSError, \ 235 "NeXus library %s could not be loaded: %s"%(file,sys.exc_info()[0]) 236 raise OSError, "Set NEXUSLIB or move NeXus to one of: %s"%(", ".join(files))
237 238 239
240 -def open(filename, mode='r'):
241 """ 242 Returns a NeXus file object. 243 """ 244 return NeXus(filename, mode)
245
246 -class NeXus(object):
247 248 # Define the interface to the dll 249 lib = _libnexus() 250 251 # ==== File ==== 252 #lib.nxiopen_.restype = c_int 253 #lib.nxiopen_.argtypes = [c_char_p, c_int, c_void_pp]
254 - def __init__(self, filename, mode='r'):
255 """ 256 Open the NeXus file returning a handle. 257 258 mode can be one of the following: 259 nxs.ACC_READ 'r' 260 nxs.ACC_RDWR 'rw' 261 nxs.ACC_CREATE 'w' 262 nxs.ACC_CREATE4 'w4' 263 nxs.ACC_CREATE5 'w5' 264 nxs.ACC_CREATEXML 'wx' 265 266 Raises RuntimeError if the file could not be opened, with the 267 filename as part of the error message. 268 269 Corresponds to NXopen(filename,mode,&handle) 270 """ 271 self.isopen = False 272 273 # Convert open mode from string to integer and check it is valid 274 if mode in _nxopen_mode: mode = _nxopen_mode[mode] 275 if mode not in _nxopen_mode.values(): 276 raise ValueError, "Invalid open mode %s",str(mode) 277 278 self.filename, self.mode = filename, mode 279 self.handle = c_void_p(None) 280 self.path = [] 281 status = self.lib.nxiopen_(filename,mode,_ref(self.handle)) 282 if status == ERROR: 283 if mode in [ACC_READ, ACC_RDWR]: 284 op = 'open' 285 else: 286 op = 'create' 287 raise RuntimeError, "Could not %s %s"%(op,filename) 288 self.isopen = True
289
290 - def __del__(self):
291 """ 292 Be sure to close the file before deleting the last reference. 293 """ 294 if self.isopen: self.close()
295 296
297 - def __str__(self):
298 """ 299 Return a string representation of the NeXus file handle. 300 """ 301 return "NeXus('%s')"%self.filename
302 303
304 - def open(self):
305 """ 306 Opens the NeXus file handle if it is not already open. 307 """ 308 if self.isopen: return 309 if self.mode==ACC_READ: 310 mode = ACC_READ 311 else: 312 mode = ACC_RDWR 313 status = self.lib.nxiopen_(self.filename,mode,_ref(self.handle)) 314 if status == ERROR: 315 raise RuntimeError, "Could not open %s"%(self.filename) 316 self.path = []
317 318 #lib.nxiclose_.restype = c_int 319 #lib.nxiclose_.argtypes = [c_void_pp]
320 - def close(self):
321 """ 322 Close the NeXus file associated with handle. 323 324 Raises RuntimeError if file could not be opened. 325 326 Corresponds to NXclose(&handle) 327 """ 328 if self.isopen: 329 self.isopen = False 330 status = self.lib.nxiclose_(_ref(self.handle)) 331 if status == ERROR: 332 raise RuntimeError, "Could not close NeXus file %s"%(self.filename) 333 self.path = []
334 335 lib.nxiflush_.restype = c_int 336 lib.nxiflush_.argtypes = [c_void_pp]
337 - def flush(self):
338 """ 339 Flush all data to the NeXus file. 340 341 Raises RuntimeError if this fails. 342 343 Corresponds to NXflush(&handle) 344 """ 345 status = self.lib.nxiflush_(_ref(self.handle)) 346 if status == ERROR: 347 raise RuntimeError, "Could not flush NeXus file %s"%(self.filename)
348 349 lib.nxisetnumberformat_.restype = c_int 350 lib.nxisetnumberformat_.argtypes = [c_void_p, c_int, c_char_p]
351 - def setnumberformat(self,type,format):
352 """ 353 Set the output format for the numbers of the given type (only 354 applies to XML). 355 356 Raises ValueError if this fails. 357 358 Corresponds to NXsetnumberformat(&handle,type,format) 359 """ 360 type = _nxtype_code[type] 361 status = self.lib.nxisetnumberformat_(self.handle,type,format) 362 if status == ERROR: 363 raise RuntimeError,\ 364 "Could not set %s to %s in %s"%(type,format,self.filename)
365 366 # ==== Group ==== 367 lib.nximakegroup_.restype = c_int 368 lib.nximakegroup_.argtypes = [c_void_p, c_char_p, c_char_p]
369 - def makegroup(self, name, nxclass):
370 """ 371 Create the group nxclass:name. 372 373 Raises RuntimeError if the group could not be created. 374 375 Corresponds to NXmakegroup(handle, name, nxclass) 376 """ 377 status = self.lib.nximakegroup_(self.handle, name, nxclass) 378 if status == ERROR: 379 raise RuntimeError,\ 380 "Could not create %s:%s in %s"%(nxclass,name,self._loc())
381 382 lib.nxiopenpath_.restype = c_int 383 lib.nxiopenpath_.argtypes = [c_void_p, c_char_p]
384 - def openpath(self, path):
385 """ 386 Open a particular group '/path/to/group'. Paths can 387 be relative to the currently open group. 388 389 Raises ValueError. 390 391 Corresponds to NXopenpath(handle, path) 392 """ 393 status = self.lib.nxiopenpath_(self.handle, path) 394 if status == ERROR: 395 raise ValueError, "Could not open %s in %s"%(path,self._loc()) 396 n,path,nxclass = self.getgroupinfo() 397 if path != 'root': 398 self.path = path.split('/') 399 else: 400 self.path = []
401 402 403 lib.nxiopengrouppath_.restype = c_int 404 lib.nxiopengrouppath_.argtypes = [c_void_p, c_char_p]
405 - def opengrouppath(self, path):
406 """ 407 Open a particular group '/path/to/group', or the dataset containing 408 the group if the path refers to a dataset. Paths can be relative to 409 the currently open group. 410 411 Raises ValueError. 412 413 Corresponds to NXopengrouppath(handle, path) 414 """ 415 status = self.lib.nxiopengrouppath_(self.handle, path) 416 if status == ERROR: 417 raise ValueError, "Could not open %s in %s"%(path,self.filename) 418 n,path,nxclass = self.getgroupinfo() 419 if path != 'root': 420 self.path = path.split('/') 421 else: 422 self.path = []
423 424 425 lib.nxiopengroup_.restype = c_int 426 lib.nxiopengroup_.argtypes = [c_void_p, c_char_p, c_char_p]
427 - def opengroup(self, name, nxclass):
428 """ 429 Open the group nxclass:name. 430 431 Raises ValueError if the group could not be opened. 432 433 Corresponds to NXopengroup(handle, name, nxclass) 434 """ 435 #print "open group",nxclass,name 436 status = self.lib.nxiopengroup_(self.handle, name, nxclass) 437 if status == ERROR: 438 raise ValueError,\ 439 "Could not open %s:%s in %s"%(nxclass,name,self._loc()) 440 self.path.append(name)
441 442 lib.nxiclosegroup_.restype = c_int 443 lib.nxiclosegroup_.argtypes = [c_void_p]
444 - def closegroup(self):
445 """ 446 Close the currently open group. 447 448 Raises RuntimeError if the group could not be closed. 449 450 Corresponds to NXclosegroup(handle) 451 """ 452 #print "close group" 453 status = self.lib.nxiclosegroup_(self.handle) 454 group = self.path.pop() 455 if status == ERROR: 456 raise RuntimeError, "Could not close %s:"%(group,self._loc())
457 458 lib.nxigetinfo_.restype = c_int 459 lib.nxigetinfo_.argtypes = [c_void_p, c_int_p, c_char_p, c_char_p]
460 - def getgroupinfo(self):
461 """ 462 Query the currently open group returning the tuple 463 numentries, path, nxclass. The path consists of names 464 of subgroups starting at the root separated by "/". 465 466 Raises ValueError if the group could not be opened. 467 468 Corresponds to NXgetgroupinfo(handle) 469 """ 470 # Space for the returned strings 471 path = ctypes.create_string_buffer(MAXPATHLEN) 472 nxclass = ctypes.create_string_buffer(MAXNAMELEN) 473 n = c_int(0) 474 status = self.lib.nxigetgroupinfo_(self.handle,_ref(n),path,nxclass) 475 if status == ERROR: 476 raise ValueError, "Could not get group info: %s"%(self._loc()) 477 #print "group info",nxclass.value,name.value,n.value 478 return n.value,path.value,nxclass.value
479 480 lib.nxiinitgroupdir_.restype = c_int 481 lib.nxiinitgroupdir_.argtypes = [c_void_p]
482 - def initgroupdir(self):
483 """ 484 Reset getnextentry to return the first entry in the group. 485 486 Raises RuntimeError if this fails. 487 488 Corresponds to NXinitgroupdir(handle) 489 """ 490 status = self.lib.nxiinitgroupdir_(self.handle) 491 if status == ERROR: 492 raise RuntimeError, \ 493 "Could not reset group scan: %s"%(self._loc())
494 495 lib.nxigetnextentry_.restype = c_int 496 lib.nxigetnextentry_.argtypes = [c_void_p, c_char_p, c_char_p, c_int_p]
497 - def getnextentry(self):
498 """ 499 Return the next entry in the group as name,nxclass tuple. 500 501 Raises RuntimeError if this fails, or if there is no next entry. 502 503 Corresponds to NXgetnextentry(handle,name,nxclass,&storage). 504 505 This function doesn't return the storage class for data entries 506 since getinfo returns shape and storage, both of which are required 507 to read the data. 508 """ 509 name = ctypes.create_string_buffer(MAXNAMELEN) 510 nxclass = ctypes.create_string_buffer(MAXNAMELEN) 511 storage = c_int(0) 512 status = self.lib.nxigetnextentry_(self.handle,name,nxclass,_ref(storage)) 513 if status == ERROR or status == EOD: 514 raise RuntimeError, \ 515 "Could not get next entry: %s"%(self._loc()) 516 ## Note: ignoring storage --- it is useless without dimensions 517 #if nxclass == 'SDS': 518 # dtype = _pytype_code(storage.value) 519 #print "group next",nxclass.value, name.value, storage.value 520 return name.value,nxclass.value
521
522 - def entries(self):
523 """ 524 Iterator of entries. 525 526 for name,nxclass in nxs.entries(): 527 process(name,nxclass) 528 529 This automatically opens the corresponding group/data for you, 530 and closes it when you are done. Do not rely on any paths 531 remaining open between entries as we restore the current 532 path each time. 533 534 This does not correspond to an existing NeXus API function, 535 but instead combines the work of initgroupdir/getnextentry 536 and open/close on data and group. 537 """ 538 # To preserve the semantics we must read in the whole list 539 # first, then process the entries one by one. Keep track 540 # of the path so we can restore it between entries. 541 n,path,_ = self.getgroupinfo() 542 #print "path",path 543 if not path == "root": 544 path = "/"+path 545 else: 546 path = "/" 547 548 # Read list of entries 549 self.initgroupdir() 550 L = [] 551 for i in range(n): 552 L.append(self.getnextentry()) 553 for name,nxclass in L: 554 self.openpath(path) # Reset the file cursor 555 if nxclass == "SDS": 556 self.opendata(name) 557 else: 558 self.opengroup(name,nxclass) 559 yield name,nxclass
560 561 # ==== Data ==== 562 lib.nxigetinfo_.restype = c_int 563 lib.nxigetinfo_.argtypes = [c_void_p, c_int_p, c_void_p, c_int_p]
564 - def getinfo(self):
565 """ 566 Returns the tuple dimensions,type for the currently open dataset. 567 Dimensions is an integer array whose length corresponds to the rank 568 of the dataset and whose elements are the size of the individual 569 dimensions. Storage type is returned as a string, with 'char' for 570 a stored string, '[u]int[8|16|32]' for various integer values or 571 'float[32|64]' for floating point values. No support for 572 complex values. 573 574 Raises RuntimeError if this fails. 575 576 Note that this is the recommended way to establish if you have 577 a dataset open. 578 579 Corresponds to NXgetinfo(handle, &rank, dims, &storage), 580 but with storage converted from HDF values to numpy compatible 581 strings, and rank implicit in the length of the returned dimensions. 582 """ 583 rank = c_int(0) 584 shape = numpy.zeros(MAXRANK, 'i') 585 storage = c_int(0) 586 status = self.lib.nxigetinfo_(self.handle, _ref(rank), shape.ctypes.data, 587 _ref(storage)) 588 if status == ERROR: 589 raise RuntimeError, "Could not get data info: %s"%(self._loc()) 590 shape = shape[:rank.value]+0 591 dtype = _pytype_code[storage.value] 592 #print "data info",shape,dtype 593 return shape,dtype
594 595 lib.nxiopendata_.restype = c_int 596 lib.nxiopendata_.argtypes = [c_void_p, c_char_p]
597 - def opendata(self, name):
598 """ 599 Open the named data set within the current group. 600 601 Raises ValueError if could not open the dataset. 602 603 Corresponds to NXopendata(handle, name) 604 """ 605 #print "opening data",name 606 status = self.lib.nxiopendata_(self.handle, name) 607 if status == ERROR: 608 raise ValueError, "Could not open data %s: %s"%(name, self._loc()) 609 self.path.append(name)
610 611 lib.nxiclosedata_.restype = c_int 612 lib.nxiclosedata_.argtypes = [c_void_p]
613 - def closedata(self):
614 """ 615 Close the currently open data set. 616 617 Raises RuntimeError if this fails (e.g., because no 618 dataset is open). 619 620 Corresponds to NXclosedata(handle) 621 """ 622 #print "closing data" 623 status = self.lib.nxiclosedata_(self.handle) 624 name = self.path.pop() 625 if status == ERROR: 626 raise RuntimeError,\ 627 "Could not close data %s: %s"%(name,self._loc())
628 629 lib.nximakedata_.restype = c_int 630 lib.nximakedata_.argtypes = [c_void_p, c_char_p, c_int, c_int, c_int_p]
631 - def makedata(self, name, dtype=None, shape=None):
632 """ 633 Create a data element of the given type and shape. See getinfo 634 for details on types. This does not open the data for writing. 635 636 Set the first dimension to nxs.UNLIMITED, for extensible data sets, 637 and use putslab to write individual slabs. 638 639 Raises ValueError if it fails. 640 641 Corresponds to NXmakedata(handle,name,type,rank,dims) 642 """ 643 # TODO: With keywords for compression and chunks, this can act as 644 # TODO: compmakedata. 645 # TODO: With keywords for value and attr, this can be used for 646 # TODO: makedata, opendata, putdata, putattr, putattr, ..., closedata 647 #print "Data",name,dtype,shape 648 storage = _nxtype_code[str(dtype)] 649 shape = numpy.array(shape,'i') 650 status = self.lib.nximakedata_(self.handle,name,storage,len(shape), 651 shape.ctypes.data_as(c_int_p)) 652 if status == ERROR: 653 raise ValueError, "Could not create data %s: %s"%(name,self._loc())
654 655 lib.nxicompmakedata_.restype = c_int 656 lib.nxicompmakedata_.argtypes = [c_void_p, c_char_p, c_int, c_int, c_int_p, 657 c_int, c_int_p]
658 - def compmakedata(self, name, dtype=None, shape=None, mode='lzw', 659 chunks=None):
660 """ 661 Create a data element of the given dimensions and type. See 662 getinfo for details on types. Compression mode is one of 663 'none', 'lzw', 'rle' or 'huffman'. chunks gives the alignment 664 of the compressed chunks in the data file. There should be one 665 chunk size for each dimension in the data. 666 667 Defaults to mode='lzw' with chunk size set to the length of the 668 fastest varying dimension. 669 670 Raises ValueError if it fails. 671 672 Corresponds to NXmakedata(handle,name,type,rank,dims). 673 """ 674 storage = _nxtype_code[str(dtype)] 675 # Make sure shape/chunk_shape are integers; hope that 32/64 bit issues 676 # with the c int type sort themselves out. 677 dims = numpy.array(shape,'i') 678 if chunks == None: 679 chunks = numpy.ones(dims.shape) 680 chunks[-1] = shape[-1] 681 else: 682 chunks = numpy.array(chunks,'i') 683 status = self.lib.nxicompmakedata_(self.handle,name,storage,len(dims), 684 dims.ctypes.data_as(c_int_p), 685 _compression_code[mode], 686 chunks.ctypes.data_as(c_int_p)) 687 if status == ERROR: 688 raise ValueError, \ 689 "Could not create compressed data %s: %s"%(name,self._loc())
690 691 lib.nxigetdata_.restype = c_int 692 lib.nxigetdata_.argtypes = [c_void_p, c_void_p]
693 - def getdata(self):
694 """ 695 Return the data. If data is a string (1-D char array), a python 696 string is returned. If data is a scalar (1-D numeric array of 697 length 1), a python numeric scalar is returned. 698 699 Raises RuntimeError if this fails. 700 701 Corresponds to NXgetdata(handle, data) 702 """ 703 # TODO: consider accepting preallocated data so we don't thrash memory 704 shape,dtype = self.getinfo() 705 datafn,pdata,size = self._poutput(dtype,shape) 706 status = self.lib.nxigetdata_(self.handle,pdata) 707 if status == ERROR: 708 raise ValueError, "Could not read data: %s"%(self._loc()) 709 #print "data",ret() 710 return datafn()
711 712 lib.nxigetslab_.restype = c_int 713 lib.nxigetslab_.argtypes = [c_void_p, c_void_p, c_int_p, c_int_p]
714 - def getslab(self, slab_offset, slab_shape):
715 """ 716 Get a slab from the data array. 717 718 Offsets are 0-origin. Shape can be inferred from the data. 719 Offset and shape must each have one entry per dimension. 720 721 Raises ValueError if this fails. 722 723 Corresponds to NXgetslab(handle,data,offset,shape) 724 """ 725 # TODO: consider accepting preallocated data so we don't thrash memory 726 shape,dtype = self.getinfo() 727 datafn,pdata,size = self._poutput(dtype,slab_shape) 728 slab_offset = numpy.array(slab_offset,'i') 729 slab_shape = numpy.array(slab_shape,'i') 730 status = self.lib.nxigetslab_(self.handle,pdata, 731 slab_offset.ctypes.data_as(c_int_p), 732 slab_shape.ctypes.data_as(c_int_p)) 733 #print "slab",offset,size,data 734 if status == ERROR: 735 raise ValueError, "Could not read slab: %s"%(self._loc()) 736 return datafn()
737 738 lib.nxiputdata_.restype = c_int 739 lib.nxiputdata_.argtypes = [c_void_p, c_void_p]
740 - def putdata(self, data):
741 """ 742 Write data into the currently open data block. 743 744 Raises ValueError if this fails. 745 746 Corresponds to NXputdata(handle, data) 747 """ 748 shape,dtype = self.getinfo() 749 data,pdata = self._pinput(data,dtype,shape) 750 status = self.lib.nxiputdata_(self.handle,pdata) 751 if status == ERROR: 752 raise ValueError, "Could not write data: %s"%(self._loc())
753 754 lib.nxiputslab_.restype = c_int 755 lib.nxiputslab_.argtypes = [c_void_p, c_void_p, c_int_p, c_int_p]
756 - def putslab(self, data, slab_offset, slab_shape):
757 """ 758 Put a slab into the data array. 759 760 Offsets are 0-origin. Shape can be inferred from the data. 761 Offset and shape must each have one entry per dimension. 762 763 Raises ValueError if this fails. 764 765 Corresponds to NXputslab(handle,data,offset,shape) 766 """ 767 shape,dtype = self.getinfo() 768 data,pdata = self._pinput(data,dtype,slab_shape) 769 slab_offset = numpy.array(slab_offset,'i') 770 slab_shape = numpy.array(slab_shape,'i') 771 #print "slab",offset,size,data 772 status = self.lib.nxiputslab_(self.handle,pdata, 773 slab_offset.ctypes.data_as(c_int_p), 774 slab_shape.ctypes.data_as(c_int_p)) 775 if status == ERROR: 776 raise ValueError, "Could not write slab: %s"%(self._loc())
777 778 779 780 # ==== Attributes ==== 781 lib.nxiinitattrdir_.restype = c_int 782 lib.nxiinitattrdir_.argtypes = [c_void_p]
783 - def initattrdir(self):
784 """ 785 Reset the getnextattr list to the first attribute. 786 787 Raises RuntimeError if this fails. 788 789 Corresponds to NXinitattrdir(handle) 790 """ 791 status = self.lib.nxiinitattrdir_(self.handle) 792 if status == ERROR: 793 raise RuntimeError, \ 794 "Could not reset attribute list: %s"%(self._loc())
795 796 lib.nxigetattrinfo_.restype = c_int 797 lib.nxigetattrinfo_.argtypes = [c_void_p, c_int_p]
798 - def getattrinfo(self):
799 """ 800 Returns the number of attributes for the currently open 801 group/data object. Do not call getnextattr() more than 802 this number of times. 803 804 Raises RuntimeError if this fails. 805 806 Corresponds to NXgetattrinfo(handl, &n) 807 """ 808 n = c_int(0) 809 status = self.lib.nxigetattrinfo_(self.handle,_ref(n)) 810 if status == ERROR: 811 raise RuntimeError, "Could not get attr info: %s"%(self._loc()) 812 #print "num attrs",n.value 813 return n.value
814 815 lib.nxigetnextattr_.restype = c_int 816 lib.nxigetnextattr_.argtypes = [c_void_p, c_char_p, c_int_p, c_int_p]
817 - def getnextattr(self):
818 """ 819 Returns the name, length, and data type for the next attribute. 820 Call getattrinfo to determine the number of attributes before 821 calling getnextattr. Data type is returned as a string. See 822 getinfo for details. Length is the number of elements in the 823 attribute. 824 825 Raises RuntimeError if NeXus returns ERROR or EOD. 826 827 Corresponds to NXgetnextattr(handle,name,&length,&storage) 828 but with storage converted from HDF values to numpy compatible 829 strings. 830 831 Note: NeXus API documentation seems to say that length is the number 832 of bytes required to store the entire attribute. 833 """ 834 name = ctypes.create_string_buffer(MAXNAMELEN) 835 length = c_int(0) 836 storage = c_int(0) 837 status = self.lib.nxigetnextattr_(self.handle,name,_ref(length),_ref(storage)) 838 if status == ERROR or status == EOD: 839 raise RuntimeError, "Could not get next attr: %s"%(self._loc()) 840 dtype = _pytype_code[storage.value] 841 #print "next attr",name.value,length.value,dtype 842 return name.value, length.value, dtype
843 844 # TODO: Resolve discrepency between NeXus API documentation and 845 # TODO: apparent behaviour for getattr/putattr length. 846 lib.nxigetattr_.restype = c_int 847 lib.nxigetattr_.argtypes = [c_void_p, c_char_p, c_void_p, c_int_p, c_int_p]
848 - def getattr(self, name, length, dtype):
849 """ 850 Returns the value of the named attribute. Requires length and 851 data type from getnextattr to allocate the appropriate amount of 852 space for the attribute. 853 854 Corresponds to NXgetattr(handle,name,data,&length,&storage) 855 """ 856 datafn,pdata,size = self._poutput(str(dtype),[length]) 857 storage = c_int(_nxtype_code[str(dtype)]) 858 #print "retrieving",name,length,dtype,size 859 size = c_int(size) 860 status = self.lib.nxigetattr_(self.handle,name,pdata,_ref(size),_ref(storage)) 861 if status == ERROR: 862 raise ValueError, "Could not read attr %s: %s" % (name,self._loc()) 863 #print "attr",name,datafn(),size 864 return datafn()
865 866 lib.nxiputattr_.restype = c_int 867 lib.nxiputattr_.argtypes = [c_void_p, c_char_p, c_void_p, c_int, c_int]
868 - def putattr(self, name, value, dtype = None):
869 """ 870 Saves the named attribute. The attribute value is a string 871 or a scalar. 872 873 Raises ValueError if the attribute could not be saved. 874 875 Corresponds to NXputattr(handle,name,data,length,storage) 876 877 Note length is the number of elements to write rather 878 than the number of bytes to write. 879 """ 880 # Establish attribute type 881 if dtype == None: 882 # Type is inferred from value 883 if hasattr(value,'dtype'): 884 dtype = str(value.dtype) 885 elif _is_string_like(value): 886 dtype = 'char' 887 else: 888 value = numpy.array(value) 889 dtype = str(value.dtype) 890 else: 891 # Set value to type 892 dtype = str(dtype) 893 if dtype == 'char' and not _is_string_like(value): 894 raise TypeError, "Expected string for 'char' attribute value" 895 if dtype != 'char': 896 value = numpy.array(value,dtype=dtype) 897 898 # Determine shape 899 if dtype == 'char': 900 length = len(value) 901 data = value 902 elif numpy.prod(value.shape) != 1: 903 # NAPI silently ignores attribute arrays 904 raise TypeError, "Attribute value must be scalar or string" 905 else: 906 length = 1 907 data = value.ctypes.data 908 909 # Perform the call 910 storage = c_int(_nxtype_code[dtype]) 911 status = self.lib.nxiputattr_(self.handle,name,data,length,storage) 912 if status == ERROR: 913 raise ValueError, "Could not write attr %s: %s"%(name,self._loc())
914
915 - def attrs(self):
916 """ 917 Iterate over attributes. 918 919 for name,value in file.attrs(): 920 process(name,value) 921 922 This automatically reads the attributes of the group/data. Do not 923 change the active group/data while processing the list. 924 925 This does not correspond to an existing NeXus API function, but 926 combines the work of attrinfo/initattrdir/getnextattr/getattr. 927 """ 928 self.initattrdir() 929 n = self.getattrinfo() 930 for i in range(n): 931 name,length,dtype = self.getnextattr() 932 value = self.getattr(name,length,dtype) 933 yield name,value
934 935 # ==== Linking ==== 936 lib.nxigetgroupid_.restype = c_int 937 lib.nxigetgroupid_.argtypes = [c_void_p, c_NXlink_p]
938 - def getgroupID(self):
939 """ 940 Return the id of the current group so we can link to it later. 941 942 Raises RuntimeError 943 944 Corresponds to NXgetgroupID(handle, &ID) 945 """ 946 ID = _NXlink() 947 status = self.lib.nxigetgroupid_(self.handle,_ref(ID)) 948 if status == ERROR: 949 raise RuntimeError, "Could not link to group: %s"%(self._loc()) 950 return ID
951 952 lib.nxigetdataid_.restype = c_int 953 lib.nxigetdataid_.argtypes = [c_void_p, c_NXlink_p]
954 - def getdataID(self):
955 """ 956 Return the id of the current data so we can link to it later. 957 958 Raises RuntimeError 959 960 Corresponds to NXgetdataID(handle, &ID) 961 """ 962 ID = _NXlink() 963 status = self.lib.nxigetdataid_(self.handle,_ref(ID)) 964 if status == ERROR: 965 raise RuntimeError, "Could not link to data: %s"%(self._loc()) 966 return ID
967 968 lib.nximakelink_.restype = c_int 969 lib.nximakelink_.argtypes = [c_void_p, c_NXlink_p] 982 983 lib.nximakenamedlink_.restype = c_int 984 lib.nximakenamedlink_.argtypes = [c_void_p, c_char_p, c_NXlink_p] 997 998 lib.nxisameid_.restype = c_int 999 lib.nxisameid_.argtypes = [c_void_p, c_NXlink_p, c_NXlink_p]
1000 - def sameID(self, ID1, ID2):
1001 """ 1002 Return True of ID1 and ID2 point to the same group/data. 1003 1004 This should not raise any errors. 1005 1006 Corresponds to NXsameID(handle,&ID1,&ID2) 1007 """ 1008 status = self.lib.nxisameid_(self.handle, _ref(ID1), _ref(ID2)) 1009 return status == OK
1010 1011 lib.nxiopensourcegroup_.restype = c_int 1012 lib.nxiopensourcegroup_.argtyps = [c_void_p]
1013 - def opensourcegroup(self):
1014 """ 1015 If the current node is a linked to another group or data, then 1016 open the group or data that it is linked to. 1017 1018 Note: it is unclear how can we tell if we are linked, other than 1019 perhaps the existence of a 'target' attribute in the current item. 1020 1021 Raises RuntimeError 1022 1023 Corresponds to NXopensourcegroup(handle) 1024 """ 1025 status = self.lib.nxiopensourcegroup_(self.handle) 1026 if status == ERROR: 1027 raise RuntimeError, "Could not open source group: %s"%(self._loc())
1028 1055 1056 # ==== External linking ==== 1057 lib.nxiinquirefile_.restype = c_int 1058 lib.nxiinquirefile_.argtypes = [c_void_p, c_char_p, c_int]
1059 - def inquirefile(self, maxnamelen=MAXPATHLEN):
1060 """ 1061 Return the filename for the current file. This may be different 1062 from the file that was opened (file.filename) if the current 1063 group is an external link to another file. 1064 1065 Raises RuntimeError if this fails. 1066 1067 Corresponds to NXinquirefile(&handle,file,len) 1068 """ 1069 filename = ctypes.create_string_buffer(maxnamelen) 1070 status = self.lib.nxiinquirefile_(self.handle,filename,maxnamelen) 1071 if status == ERROR: 1072 raise RuntimeError,\ 1073 "Could not determine filename: %s"%(self._loc()) 1074 return filename.value
1075 1076 lib.nxilinkexternal_.restype = c_int 1077 lib.nxilinkexternal_.argtyps = [c_void_p, c_char_p, 1078 c_char_p, c_char_p]
1079 - def linkexternal(self, name, nxclass, url):
1080 """ 1081 Return the filename for the external link if there is one, 1082 otherwise return None. 1083 1084 Corresponds to NXisexternalgroup(&handle,name,nxclass,file,len) 1085 """ 1086 status = self.lib.nxilinkexternal_(self.handle,name,nxclass,url) 1087 if status == ERROR: 1088 raise RuntimeError,\ 1089 "Could not link %s to %s: %s"%(name,url,self._loc())
1090 1091 1092 1093 lib.nxiisexternalgroup_.restype = c_int 1094 lib.nxiisexternalgroup_.argtyps = [c_void_p, c_char_p, 1095 c_char_p, c_char_p, c_int]
1096 - def isexternalgroup(self, name, nxclass, maxnamelen=MAXPATHLEN):
1097 """ 1098 Return the filename for the external link if there is one, 1099 otherwise return None. 1100 1101 Corresponds to NXisexternalgroup(&handle,name,nxclass,file,len) 1102 """ 1103 url = ctypes.create_string_buffer(maxnamelen) 1104 status = self.lib.nxiisexternalgroup_(self.handle,name,nxclass, 1105 url,maxnamelen) 1106 if status == ERROR: 1107 return None 1108 else: 1109 return url.value
1110 1111 # ==== Utility functions ====
1112 - def _loc(self):
1113 """ 1114 Return file location as string filename:path 1115 1116 This is an extension to the NeXus API. 1117 """ 1118 if not self.path == []: 1119 pathstr = "/".join(self.path) 1120 else: 1121 pathstr = "root" 1122 return "%s(%s)"%(self.filename,pathstr)
1123
1124 - def _poutput(self, dtype, shape):
1125 """ 1126 Build space to collect a nexus data element. 1127 Returns datafn,data,size where 1128 - datafn is a lamba expression to extract the value out of the element. 1129 - pdata is the value to pass to C (effectively a void *) 1130 - size is the number of bytes in the data block 1131 Note that ret can return a string, a scalar or an array depending 1132 on the data type and shape of the data group. 1133 """ 1134 if len(shape) == 1 and dtype == 'char': 1135 # string - use ctypes allocator 1136 size = int(shape[0]) 1137 pdata = ctypes.create_string_buffer(size) 1138 datafn = lambda: pdata.value 1139 else: 1140 # numeric - use numpy array 1141 if dtype=='char': dtype = 'uint8' 1142 data = numpy.zeros(shape,dtype) 1143 if len(shape) == 1 and shape[0] == 1: 1144 datafn = lambda: data[0] 1145 else: 1146 datafn = lambda: data 1147 pdata = data.ctypes.data 1148 size = data.nbytes 1149 return datafn,pdata,size
1150
1151 - def _pinput(self, data, dtype, shape):
1152 """ 1153 Convert an input array to a C pointer to a dense array. This may 1154 require conversion of the array, so the new array is returned along 1155 with its pointer. 1156 """ 1157 if dtype == "char": 1158 # Character data - pad with zeros to the right length 1159 if not _is_string_like(data): 1160 raise ValueError,"Expected character data: %s"%(self._loc()) 1161 if len(data) < shape[0]: 1162 data += '\000'*(shape[0]-len(data)) 1163 else: 1164 # Convert scalars to vectors of length one 1165 if numpy.prod(shape) == 1 and not hasattr(data,'shape'): 1166 data = numpy.array([data],dtype=dtype) 1167 # Check that dimensions match 1168 # Ick! need to exclude dimensions of length 1 in order to catch 1169 # array slices such as a[:,1], which only report one dimension 1170 input_shape = numpy.array([i for i in data.shape if i != 1]) 1171 target_shape = numpy.array([i for i in shape if i != 1]) 1172 if len(input_shape) != len(target_shape) or (input_shape != target_shape).any(): 1173 raise ValueError,\ 1174 "Shape mismatch %s!=%s: %s"%(data.shape,shape,self.filename) 1175 if str(data.dtype) != dtype: 1176 raise ValueError,\ 1177 "Type mismatch %s!=%s: %s"%(dtype,data.dtype,self._loc()) 1178 1179 if dtype == 'char': 1180 # String: hand it over as usual for strings. Assumes the string 1181 # is the correct length for the storage area. 1182 pdata = data 1183 else: 1184 # Vector: assume it is of the correct storage class and size 1185 data = numpy.ascontiguousarray(data) 1186 pdata = data.ctypes.data 1187 1188 return data,pdata
1189
1190 - def show(self, path=None, indent=0):
1191 """ 1192 Print the structure of a NeXus file from the current node. 1193 1194 TODO: Break this into a tree walker and a visitor. 1195 """ 1196 oldpath = "/"+"/".join(self.path) 1197 if not path: path = oldpath 1198 self.openpath(path) 1199 1200 print "=== File",self.inquirefile(),path 1201 self._show(indent=indent) 1202 self.openpath(oldpath)
1203
1204 - def _show(self, indent=0):
1205 """ 1206 Print the structure of a NeXus file from the current node. 1207 1208 TODO: Break this into a tree walker and a visitor. 1209 """ 1210 prefix = ' '*indent 1211 link = self.link() 1212 if link: 1213 print "%(prefix)s-> %(link)s" % locals() 1214 return 1215 for attr,value in self.attrs(): 1216 print "%(prefix)s@%(attr)s: %(value)s" % locals() 1217 for name,nxclass in self.entries(): 1218 if nxclass == "SDS": 1219 shape,dtype = self.getinfo() 1220 dims = "x".join([str(x) for x in shape]) 1221 print "%(prefix)s%(name)s %(dtype)s %(dims)s" % locals() 1222 link = self.link() 1223 if link: 1224 print " %(prefix)s-> %(link)s" % locals() 1225 else: 1226 for attr,value in self.attrs(): 1227 print " %(prefix)s@%(attr)s: %(value)s" % locals() 1228 if numpy.prod(shape) < 8: 1229 value = self.getdata() 1230 print " %s%s"%(prefix,str(value)) 1231 else: 1232 print "%(prefix)s%(name)s %(nxclass)s" % locals() 1233 self._show(indent=indent+2)
1234 1235 1236 __id__ = "$ID$" 1237