| 1 | # This program is public domain |
|---|
| 2 | # Author: Paul Kienzle |
|---|
| 3 | |
|---|
| 4 | """@package nxs.napi |
|---|
| 5 | Wrapper for the NeXus shared library. |
|---|
| 6 | |
|---|
| 7 | Use this interface when converting code from other languages which |
|---|
| 8 | do not support the natural view of the hierarchy. |
|---|
| 9 | |
|---|
| 10 | Library Location |
|---|
| 11 | ================ |
|---|
| 12 | |
|---|
| 13 | This wrapper needs the location of the libNeXus precompiled binary. It |
|---|
| 14 | looks in the following places in order:: |
|---|
| 15 | |
|---|
| 16 | @verbatim |
|---|
| 17 | os.environ['NEXUSLIB'] - All |
|---|
| 18 | directory containing nxs.py - All |
|---|
| 19 | os.environ['NEXUSDIR']\\bin - Windows |
|---|
| 20 | os.environ['LD_LIBRARY_PATH'] - Unix |
|---|
| 21 | os.environ['DYLD_LIBRARY_PATH'] - Darwin |
|---|
| 22 | PREFIX/lib - Unix and Darwin |
|---|
| 23 | /usr/local/lib - Unix and Darwin |
|---|
| 24 | /usr/lib - Unix and Darwin |
|---|
| 25 | @endverbatim |
|---|
| 26 | |
|---|
| 27 | - On Windows it looks for one of libNeXus.dll or libNeXus-0.dll. |
|---|
| 28 | - On OS X it looks for libNeXus.0.dylib |
|---|
| 29 | - On Unix it looks for libNeXus.so.0 |
|---|
| 30 | - NEXUSDIR defaults to 'C:\\Program Files\\NeXus Data Format'. |
|---|
| 31 | - PREFIX defaults to /usr/local, but is replaced by the value of --prefix during configure. |
|---|
| 32 | |
|---|
| 33 | The import will raise an OSError exception if the library wasn't found |
|---|
| 34 | or couldn't be loaded. Note that on Windows in particular this may be |
|---|
| 35 | because the supporting HDF5 dlls were not available in the usual places. |
|---|
| 36 | |
|---|
| 37 | If you are extracting the nexus library from a bundle at runtime, set |
|---|
| 38 | os.environ['NEXUSLIB'] to the path where it is extracted before the |
|---|
| 39 | first import of nxs. |
|---|
| 40 | |
|---|
| 41 | Example |
|---|
| 42 | ======= |
|---|
| 43 | @code |
|---|
| 44 | import nxs |
|---|
| 45 | file = nxs.open('filename.nxs','rw') |
|---|
| 46 | file.opengroup('entry1') |
|---|
| 47 | file.opendata('definition') |
|---|
| 48 | print file.getdata() |
|---|
| 49 | file.close() |
|---|
| 50 | @endcode |
|---|
| 51 | |
|---|
| 52 | See @see nxstest.py for a more complete example. |
|---|
| 53 | |
|---|
| 54 | Interface |
|---|
| 55 | ========= |
|---|
| 56 | |
|---|
| 57 | When converting code to python from other languages you do not |
|---|
| 58 | necessarily want to redo the file handling code. The nxs |
|---|
| 59 | provides an interface which more closely follows the |
|---|
| 60 | NeXus application programming interface (NAPI_). |
|---|
| 61 | |
|---|
| 62 | This wrapper differs from NAPI in several respects:: |
|---|
| 63 | |
|---|
| 64 | - Data values are loaded/stored directly from numpy arrays. |
|---|
| 65 | - Return codes are turned into exceptions. |
|---|
| 66 | - The file handle is stored in a file object |
|---|
| 67 | - Constants are handled somewhat differently (see below) |
|---|
| 68 | - Type checking on data/parameter storage |
|---|
| 69 | - Adds iterators file.entries() and file.attrs() |
|---|
| 70 | - Adds link() function to return the name of the linked to group, if any |
|---|
| 71 | - NXmalloc/NXfree are not needed. |
|---|
| 72 | |
|---|
| 73 | File open modes can be constants or strings:: |
|---|
| 74 | |
|---|
| 75 | @verbatim |
|---|
| 76 | nxs.ACC_READ 'r' |
|---|
| 77 | nxs.ACC_RDWR 'rw' |
|---|
| 78 | nxs.ACC_CREATE 'w' |
|---|
| 79 | nxs.ACC_CREATE4 'w4' |
|---|
| 80 | nxs.ACC_CREATE5 'w5' |
|---|
| 81 | nxs.ACC_CREATEXML 'wx' |
|---|
| 82 | @endverbatim |
|---|
| 83 | |
|---|
| 84 | Dimension constants:: |
|---|
| 85 | |
|---|
| 86 | - nxs.UNLIMITED - for the extensible data dimension |
|---|
| 87 | - nxs.MAXRANK - for the number of possible dimensions |
|---|
| 88 | |
|---|
| 89 | Data types are strings corresponding to the numpy data types:: |
|---|
| 90 | |
|---|
| 91 | 'float32' 'float64' |
|---|
| 92 | 'int8' 'int16' 'int32' 'int64' |
|---|
| 93 | 'uint8' 'uint16' 'uint32' 'uint64' |
|---|
| 94 | |
|---|
| 95 | Use 'char' for string data. |
|---|
| 96 | |
|---|
| 97 | You can use the numpy A.dtype attribute for the type of array A. |
|---|
| 98 | |
|---|
| 99 | Dimensions are lists of integers or numpy arrays. You can use the |
|---|
| 100 | numpy A.shape attribute for the dimensions of array A. |
|---|
| 101 | |
|---|
| 102 | Compression codes are:: |
|---|
| 103 | |
|---|
| 104 | 'none' 'lzw' 'rle' 'huffman' |
|---|
| 105 | |
|---|
| 106 | As of this writing NeXus only supports 'none' and 'lzw'. |
|---|
| 107 | |
|---|
| 108 | Miscellaneous constants:: |
|---|
| 109 | |
|---|
| 110 | - nxs.MAXNAMELEN - names must be shorter than this |
|---|
| 111 | - nxs.MAXPATHLEN - total path length must be shorter than this |
|---|
| 112 | - nxs.H4SKIP - class names that may appear in HDF4 files but can be ignored |
|---|
| 113 | |
|---|
| 114 | Caveats |
|---|
| 115 | ======= |
|---|
| 116 | |
|---|
| 117 | @todo NOSTRIP constant is probably not handled properly, |
|---|
| 118 | @todo Embedded nulls in strings is not supported |
|---|
| 119 | |
|---|
| 120 | @warning We have a memory leak. Calling open/close costs about 90k a pair. |
|---|
| 121 | This is an eigenbug: |
|---|
| 122 | - if I test ctypes on a simple library it does not leak |
|---|
| 123 | - if I use the leak_test1 code in the nexus distribution it doesn't leak |
|---|
| 124 | - if I remove the open/close call in the wrapper it doesn't leak. |
|---|
| 125 | |
|---|
| 126 | """ |
|---|
| 127 | |
|---|
| 128 | ## @example nxstest.py |
|---|
| 129 | # Test program for NeXus python interface |
|---|
| 130 | |
|---|
| 131 | __all__ = ['UNLIMITED', 'MAXRANK', 'MAXNAMELEN','MAXPATHLEN','H4SKIP', |
|---|
| 132 | 'NeXus','NeXusError','open'] |
|---|
| 133 | |
|---|
| 134 | import sys, os, numpy, ctypes |
|---|
| 135 | |
|---|
| 136 | # Defined ctypes |
|---|
| 137 | from ctypes import c_void_p, c_int, c_int64, c_long, c_char, c_char_p |
|---|
| 138 | from ctypes import byref as _ref |
|---|
| 139 | c_void_pp = ctypes.POINTER(c_void_p) |
|---|
| 140 | c_int_p = ctypes.POINTER(c_int) |
|---|
| 141 | c_int64_p = ctypes.POINTER(c_int64) |
|---|
| 142 | class _NXlink(ctypes.Structure): |
|---|
| 143 | _fields_ = [("iTag", c_long), |
|---|
| 144 | ("iRef", c_long), |
|---|
| 145 | ("targetPath", c_char*1024), |
|---|
| 146 | ("linktype", c_int)] |
|---|
| 147 | _pack_ = False |
|---|
| 148 | c_NXlink_p = ctypes.POINTER(_NXlink) |
|---|
| 149 | |
|---|
| 150 | |
|---|
| 151 | # Open modes: |
|---|
| 152 | ACC_READ,ACC_RDWR,ACC_CREATE=1,2,3 |
|---|
| 153 | ACC_CREATE4,ACC_CREATE5,ACC_CREATEXML=4,5,6 |
|---|
| 154 | _nxopen_mode=dict(r=1,rw=2,w=3,w4=4,w5=5,wx=6) |
|---|
| 155 | NOSTRIP=128 |
|---|
| 156 | |
|---|
| 157 | # Status codes |
|---|
| 158 | OK,ERROR,EOD=1,0,-1 |
|---|
| 159 | |
|---|
| 160 | # Other constants |
|---|
| 161 | UNLIMITED=-1 |
|---|
| 162 | MAXRANK=32 |
|---|
| 163 | MAXNAMELEN=64 |
|---|
| 164 | MAXPATHLEN=1024 # inferred from code |
|---|
| 165 | |
|---|
| 166 | # bogus groups; these groups are ignored in HDFView from NCSA. |
|---|
| 167 | H4SKIP = ['CDF0.0','_HDF_CHK_TBL_','Attr0.0', |
|---|
| 168 | 'RIG0.0','RI0.0', 'RIATTR0.0N','RIATTR0.0C'] |
|---|
| 169 | |
|---|
| 170 | # HDF data types from numpy types |
|---|
| 171 | _nxtype_code=dict( |
|---|
| 172 | char=4, |
|---|
| 173 | float32=5,float64=6, |
|---|
| 174 | int8=20,uint8=21, |
|---|
| 175 | int16=22,uint16=23, |
|---|
| 176 | int32=24,uint32=25, |
|---|
| 177 | int64=26,uint64=27, |
|---|
| 178 | ) |
|---|
| 179 | # Python types from HDF data types |
|---|
| 180 | # Other than 'char' for the string type, the python types correspond to |
|---|
| 181 | # the numpy data types, and can be used directly to create numpy arrays. |
|---|
| 182 | # Note: put this in a lambda to hide v,k from the local namespace |
|---|
| 183 | _pytype_code=(lambda : dict([(v,k) for (k,v) in _nxtype_code.iteritems()]))() |
|---|
| 184 | |
|---|
| 185 | # Compression to use when creating data blocks |
|---|
| 186 | _compression_code=dict( |
|---|
| 187 | none=100, |
|---|
| 188 | lzw=200, |
|---|
| 189 | rle=300, |
|---|
| 190 | huffman=400) |
|---|
| 191 | |
|---|
| 192 | def _is_string_like(obj): |
|---|
| 193 | """ |
|---|
| 194 | Return True if object acts like a string. |
|---|
| 195 | """ |
|---|
| 196 | # From matplotlib cbook.py John D. Hunter |
|---|
| 197 | # Python 2.2 style licence. See license.py in matplotlib for details. |
|---|
| 198 | if hasattr(obj, 'shape'): return False |
|---|
| 199 | try: obj + '' |
|---|
| 200 | except (TypeError, ValueError): return False |
|---|
| 201 | return True |
|---|
| 202 | |
|---|
| 203 | def _is_list_like(obj): |
|---|
| 204 | """ |
|---|
| 205 | Return True if object acts like a list |
|---|
| 206 | """ |
|---|
| 207 | try: obj + [] |
|---|
| 208 | except TypeError: return False |
|---|
| 209 | return True |
|---|
| 210 | |
|---|
| 211 | def _libnexus(): |
|---|
| 212 | """ |
|---|
| 213 | Load the NeXus library whereever it may be. |
|---|
| 214 | """ |
|---|
| 215 | # this will get changed as part of the install process |
|---|
| 216 | # it should correspond to --prefix specified to ./configure |
|---|
| 217 | nxprefix = '/usr/local' |
|---|
| 218 | # NEXUSLIB takes precedence |
|---|
| 219 | if 'NEXUSLIB' in os.environ: |
|---|
| 220 | file = os.environ['NEXUSLIB'] |
|---|
| 221 | if not os.path.isfile(file): |
|---|
| 222 | raise OSError, \ |
|---|
| 223 | "File %s from environment variable NEXUSLIB does exist"%(file) |
|---|
| 224 | files = [file] |
|---|
| 225 | else: |
|---|
| 226 | files = [] |
|---|
| 227 | |
|---|
| 228 | # Default names and locations to look for the library are system dependent |
|---|
| 229 | filedir = os.path.dirname(__file__) |
|---|
| 230 | if sys.platform in ('win32','cygwin'): |
|---|
| 231 | # NEXUSDIR is set by the Windows installer for NeXus |
|---|
| 232 | if 'NEXUSDIR' in os.environ: |
|---|
| 233 | winnxdir = os.environ['NEXUSDIR'] |
|---|
| 234 | else: |
|---|
| 235 | winnxdir = 'C:/Program Files/NeXus Data Format' |
|---|
| 236 | |
|---|
| 237 | files += [filedir+"/libNeXus-0.dll", |
|---|
| 238 | winnxdir + '/bin/libNeXus-0.dll', |
|---|
| 239 | filedir+"/libNeXus.dll"] |
|---|
| 240 | else: |
|---|
| 241 | if sys.platform in ('darwin'): |
|---|
| 242 | lib = 'libNeXus.0.dylib' |
|---|
| 243 | ldenv = 'DYLD_LIBRARY_PATH' |
|---|
| 244 | else: |
|---|
| 245 | lib = 'libNeXus.so.0' |
|---|
| 246 | ldenv = 'LD_LIBRARY_PATH' |
|---|
| 247 | # Search the load library path as well as the standard locations |
|---|
| 248 | ldpath = [p for p in os.environ.get(ldenv,'').split(':') if p != ''] |
|---|
| 249 | stdpath = [ nxprefix+'/lib', '/usr/local/lib', '/usr/lib'] |
|---|
| 250 | files += [os.path.join(p,lib) for p in [filedir]+ldpath+stdpath] |
|---|
| 251 | |
|---|
| 252 | # Given a list of files, try loading the first one that is available. |
|---|
| 253 | for file in files: |
|---|
| 254 | if not os.path.isfile(file): continue |
|---|
| 255 | try: |
|---|
| 256 | return ctypes.cdll[file] |
|---|
| 257 | except: |
|---|
| 258 | raise OSError, \ |
|---|
| 259 | "NeXus library %s could not be loaded: %s"%(file,sys.exc_info()) |
|---|
| 260 | raise OSError, "Set NEXUSLIB or move NeXus to one of: %s"%(", ".join(files)) |
|---|
| 261 | |
|---|
| 262 | def _init(): |
|---|
| 263 | lib = _libnexus() |
|---|
| 264 | lib.NXMDisableErrorReporting() |
|---|
| 265 | return lib |
|---|
| 266 | |
|---|
| 267 | # Define the interface to the dll |
|---|
| 268 | nxlib = _init() |
|---|
| 269 | |
|---|
| 270 | |
|---|
| 271 | def open(filename, mode='r'): |
|---|
| 272 | """ |
|---|
| 273 | Returns a NeXus file object. |
|---|
| 274 | """ |
|---|
| 275 | return NeXus(filename, mode) |
|---|
| 276 | |
|---|
| 277 | class NeXusError(Exception): |
|---|
| 278 | """NeXus Error""" |
|---|
| 279 | pass |
|---|
| 280 | |
|---|
| 281 | class NeXus(object): |
|---|
| 282 | |
|---|
| 283 | # ==== File ==== |
|---|
| 284 | nxlib.nxiopen_.restype = c_int |
|---|
| 285 | nxlib.nxiopen_.argtypes = [c_char_p, c_int, c_void_pp] |
|---|
| 286 | def __init__(self, filename, mode='r'): |
|---|
| 287 | """ |
|---|
| 288 | Open the NeXus file returning a handle. |
|---|
| 289 | |
|---|
| 290 | mode can be one of the following: |
|---|
| 291 | nxs.ACC_READ 'r' open a file read-only |
|---|
| 292 | nxs.ACC_RDWR 'rw' open a file read/write |
|---|
| 293 | nxs.ACC_CREATE 'w' open a file write |
|---|
| 294 | nxs.ACC_CREATE4 'w4' open a Nexus file with HDF4 |
|---|
| 295 | nxs.ACC_CREATE5 'w5' open a Nexus file with HDF5 |
|---|
| 296 | nxs.ACC_CREATEXML 'wx' open a Nexus file with XML |
|---|
| 297 | |
|---|
| 298 | Raises ValueError if the open mode is invalid. |
|---|
| 299 | |
|---|
| 300 | Raises NeXusError if the file could not be opened, with the |
|---|
| 301 | filename as part of the error message. |
|---|
| 302 | |
|---|
| 303 | Corresponds to NXopen(filename,mode,&handle) |
|---|
| 304 | """ |
|---|
| 305 | self.isopen = False |
|---|
| 306 | |
|---|
| 307 | # Convert open mode from string to integer and check it is valid |
|---|
| 308 | if mode in _nxopen_mode: mode = _nxopen_mode[mode] |
|---|
| 309 | if mode not in _nxopen_mode.values(): |
|---|
| 310 | raise ValueError, "Invalid open mode %s",str(mode) |
|---|
| 311 | |
|---|
| 312 | self.filename, self.mode = filename, mode |
|---|
| 313 | self.handle = c_void_p(None) |
|---|
| 314 | self._path = [] |
|---|
| 315 | self._indata = False |
|---|
| 316 | status = nxlib.nxiopen_(filename,mode,_ref(self.handle)) |
|---|
| 317 | if status == ERROR: |
|---|
| 318 | if mode in [ACC_READ, ACC_RDWR]: |
|---|
| 319 | op = 'open' |
|---|
| 320 | else: |
|---|
| 321 | op = 'create' |
|---|
| 322 | raise NeXusError, "Could not %s %s"%(op,filename) |
|---|
| 323 | self.isopen = True |
|---|
| 324 | |
|---|
| 325 | def _getpath(self): |
|---|
| 326 | mypath = [level[0] for level in self._path] |
|---|
| 327 | return '/'+'/'.join(mypath) |
|---|
| 328 | path = property(_getpath,doc="Unix-style path to node") |
|---|
| 329 | |
|---|
| 330 | def _getlongpath(self): |
|---|
| 331 | mypath = [':'.join(level) for level in self._path] |
|---|
| 332 | return '/' + '/'.join(mypath) |
|---|
| 333 | longpath = property(_getlongpath, doc="Unix-style path including " \ |
|---|
| 334 | + "nxclass to the node") |
|---|
| 335 | |
|---|
| 336 | def __del__(self): |
|---|
| 337 | """ |
|---|
| 338 | Be sure to close the file before deleting the last reference. |
|---|
| 339 | """ |
|---|
| 340 | if self.isopen: self.close() |
|---|
| 341 | |
|---|
| 342 | |
|---|
| 343 | def __str__(self): |
|---|
| 344 | """ |
|---|
| 345 | Return a string representation of the NeXus file handle. |
|---|
| 346 | """ |
|---|
| 347 | return "NeXus('%s')"%self.filename |
|---|
| 348 | |
|---|
| 349 | |
|---|
| 350 | def open(self): |
|---|
| 351 | """ |
|---|
| 352 | Opens the NeXus file handle if it is not already open. |
|---|
| 353 | |
|---|
| 354 | Raises NeXusError if the file could not be opened. |
|---|
| 355 | |
|---|
| 356 | Corresponds to NXopen(filename,mode,&handle) |
|---|
| 357 | """ |
|---|
| 358 | if self.isopen: return |
|---|
| 359 | if self.mode==ACC_READ: |
|---|
| 360 | mode = ACC_READ |
|---|
| 361 | else: |
|---|
| 362 | mode = ACC_RDWR |
|---|
| 363 | status = nxlib.nxiopen_(self.filename,mode,_ref(self.handle)) |
|---|
| 364 | if status == ERROR: |
|---|
| 365 | raise NeXusError, "Could not open %s"%(self.filename) |
|---|
| 366 | self._path = [] |
|---|
| 367 | self._indata = False |
|---|
| 368 | |
|---|
| 369 | nxlib.nxiclose_.restype = c_int |
|---|
| 370 | nxlib.nxiclose_.argtypes = [c_void_pp] |
|---|
| 371 | def close(self): |
|---|
| 372 | """ |
|---|
| 373 | Close the NeXus file associated with handle. |
|---|
| 374 | |
|---|
| 375 | Raises NeXusError if file could not be closed. |
|---|
| 376 | |
|---|
| 377 | Corresponds to NXclose(&handle) |
|---|
| 378 | """ |
|---|
| 379 | if self.isopen: |
|---|
| 380 | self.isopen = False |
|---|
| 381 | status = nxlib.nxiclose_(_ref(self.handle)) |
|---|
| 382 | if status == ERROR: |
|---|
| 383 | raise NeXusError, "Could not close NeXus file %s"%(self.filename) |
|---|
| 384 | self._path = [] |
|---|
| 385 | self._indata = False |
|---|
| 386 | |
|---|
| 387 | nxlib.nxiflush_.restype = c_int |
|---|
| 388 | nxlib.nxiflush_.argtypes = [c_void_pp] |
|---|
| 389 | def flush(self): |
|---|
| 390 | """ |
|---|
| 391 | Flush all data to the NeXus file. |
|---|
| 392 | |
|---|
| 393 | Raises NeXusError if this fails. |
|---|
| 394 | |
|---|
| 395 | Corresponds to NXflush(&handle) |
|---|
| 396 | """ |
|---|
| 397 | status = nxlib.nxiflush_(_ref(self.handle)) |
|---|
| 398 | if status == ERROR: |
|---|
| 399 | raise NeXusError, "Could not flush NeXus file %s"%(self.filename) |
|---|
| 400 | |
|---|
| 401 | nxlib.nxisetnumberformat_.restype = c_int |
|---|
| 402 | nxlib.nxisetnumberformat_.argtypes = [c_void_p, c_int, c_char_p] |
|---|
| 403 | def setnumberformat(self,type,format): |
|---|
| 404 | """ |
|---|
| 405 | Set the output format for the numbers of the given type (only |
|---|
| 406 | applies to XML). |
|---|
| 407 | |
|---|
| 408 | Raises ValueError if the number format is incorrect. |
|---|
| 409 | |
|---|
| 410 | Corresponds to NXsetnumberformat(&handle,type,format) |
|---|
| 411 | """ |
|---|
| 412 | type = _nxtype_code[type] |
|---|
| 413 | status = nxlib.nxisetnumberformat_(self.handle,type,format) |
|---|
| 414 | if status == ERROR: |
|---|
| 415 | raise ValueError,\ |
|---|
| 416 | "Could not set %s to %s in %s"%(type,format,self.filename) |
|---|
| 417 | |
|---|
| 418 | # ==== Group ==== |
|---|
| 419 | nxlib.nximakegroup_.restype = c_int |
|---|
| 420 | nxlib.nximakegroup_.argtypes = [c_void_p, c_char_p, c_char_p] |
|---|
| 421 | def makegroup(self, name, nxclass): |
|---|
| 422 | """ |
|---|
| 423 | Create the group nxclass:name. |
|---|
| 424 | |
|---|
| 425 | Raises NeXusError if the group could not be created. |
|---|
| 426 | |
|---|
| 427 | Corresponds to NXmakegroup(handle, name, nxclass) |
|---|
| 428 | """ |
|---|
| 429 | #print "makegroup",self._loc(),name,nxclass |
|---|
| 430 | status = nxlib.nximakegroup_(self.handle, name, nxclass) |
|---|
| 431 | if status == ERROR: |
|---|
| 432 | raise NeXusError,\ |
|---|
| 433 | "Could not create %s:%s in %s"%(nxclass,name,self._loc()) |
|---|
| 434 | |
|---|
| 435 | nxlib.nxiopenpath_.restype = c_int |
|---|
| 436 | nxlib.nxiopenpath_.argtypes = [c_void_p, c_char_p] |
|---|
| 437 | def openpath(self, path): |
|---|
| 438 | """ |
|---|
| 439 | Open a particular group '/path/to/group'. Paths can be |
|---|
| 440 | absolute or relative to the currently open group. If openpath |
|---|
| 441 | fails, then currently open path may not be different from the |
|---|
| 442 | starting path. For better performation the types can be |
|---|
| 443 | specified as well using '/path:type1/to:type2/group:type3' |
|---|
| 444 | which will prevent searching the file for the types associated |
|---|
| 445 | with the supplied names. |
|---|
| 446 | |
|---|
| 447 | Raises ValueError. |
|---|
| 448 | |
|---|
| 449 | Corresponds to NXopenpath(handle, path) |
|---|
| 450 | """ |
|---|
| 451 | self._openpath(path, opendata=True) |
|---|
| 452 | |
|---|
| 453 | def _openpath(self, path, opendata=True): |
|---|
| 454 | """helper function: open relative path and maybe data""" |
|---|
| 455 | # Determine target node as sequence of group names |
|---|
| 456 | if path == '/': |
|---|
| 457 | target = [] |
|---|
| 458 | else: |
|---|
| 459 | if path.endswith("/"): |
|---|
| 460 | path = path[:-1] |
|---|
| 461 | if path.startswith('/'): |
|---|
| 462 | target = path[1:].split('/') |
|---|
| 463 | else: |
|---|
| 464 | target = self._path + path.split('/') |
|---|
| 465 | |
|---|
| 466 | # Remove relative path indicators from target |
|---|
| 467 | L = [] |
|---|
| 468 | for t in target: |
|---|
| 469 | if t == '.': |
|---|
| 470 | # Skip current node |
|---|
| 471 | pass |
|---|
| 472 | elif t == '..': |
|---|
| 473 | if L == []: |
|---|
| 474 | raise ValueError("too many '..' in path") |
|---|
| 475 | L.pop() |
|---|
| 476 | else: |
|---|
| 477 | L.append(t) |
|---|
| 478 | target = L |
|---|
| 479 | |
|---|
| 480 | # split out nxclass from each level if available |
|---|
| 481 | L = [] |
|---|
| 482 | for t in target: |
|---|
| 483 | try: |
|---|
| 484 | item = t.split(":") |
|---|
| 485 | if len(item) == 1: |
|---|
| 486 | L.append((item[0], None)) |
|---|
| 487 | else: |
|---|
| 488 | L.append(tuple(item)) |
|---|
| 489 | except AttributeError: |
|---|
| 490 | L.append(t) |
|---|
| 491 | target = L |
|---|
| 492 | |
|---|
| 493 | #print "current path",self._path |
|---|
| 494 | #print "%s"%path,target |
|---|
| 495 | |
|---|
| 496 | # Find which groups need to be closed and opened |
|---|
| 497 | up = [] |
|---|
| 498 | down = [] |
|---|
| 499 | for (i, (name, nxclass)) in enumerate(target): |
|---|
| 500 | if i == len(self._path): |
|---|
| 501 | #print "target longer than current" |
|---|
| 502 | up = [] |
|---|
| 503 | down = target[i:] |
|---|
| 504 | break |
|---|
| 505 | elif self._path[i] != name: |
|---|
| 506 | #print "target and current differ at",name |
|---|
| 507 | up = self._path[i:] |
|---|
| 508 | down = target[i:] |
|---|
| 509 | break |
|---|
| 510 | else: |
|---|
| 511 | #print "target shorter than current" |
|---|
| 512 | up = self._path[len(target):] |
|---|
| 513 | down = [] |
|---|
| 514 | |
|---|
| 515 | # add more information to the down path |
|---|
| 516 | for i in xrange(len(down)): |
|---|
| 517 | try: |
|---|
| 518 | (name, nxclass) = down[i] |
|---|
| 519 | except ValueError: |
|---|
| 520 | down[i] = (down[i], None) |
|---|
| 521 | #print "close,open",up,down |
|---|
| 522 | |
|---|
| 523 | # Close groups on the way up |
|---|
| 524 | if self._indata and up != []: |
|---|
| 525 | self.closedata() |
|---|
| 526 | up.pop() |
|---|
| 527 | for target in up: |
|---|
| 528 | self.closegroup() |
|---|
| 529 | |
|---|
| 530 | # Open groups on the way down |
|---|
| 531 | for target in down: |
|---|
| 532 | (name, nxclass) = target |
|---|
| 533 | if nxclass is None: |
|---|
| 534 | nxclass = self.__getnxclass(name) |
|---|
| 535 | if nxclass != "SDS": |
|---|
| 536 | self.opengroup(name, nxclass) |
|---|
| 537 | elif opendata: |
|---|
| 538 | self.opendata(name) |
|---|
| 539 | else: |
|---|
| 540 | raise ValueError("node %s not in %s"%(name,self.path)) |
|---|
| 541 | |
|---|
| 542 | nxlib.nxiopengrouppath_.restype = c_int |
|---|
| 543 | nxlib.nxiopengrouppath_.argtypes = [c_void_p, c_char_p] |
|---|
| 544 | def opengrouppath(self, path): |
|---|
| 545 | """ |
|---|
| 546 | Open a particular group '/path/to/group', or the dataset containing |
|---|
| 547 | the group if the path refers to a dataset. Paths can be relative to |
|---|
| 548 | the currently open group. |
|---|
| 549 | |
|---|
| 550 | Raises ValueError. |
|---|
| 551 | |
|---|
| 552 | Corresponds to NXopengrouppath(handle, path) |
|---|
| 553 | """ |
|---|
| 554 | self._openpath(path,opendata=False) |
|---|
| 555 | |
|---|
| 556 | nxlib.nxiopengroup_.restype = c_int |
|---|
| 557 | nxlib.nxiopengroup_.argtypes = [c_void_p, c_char_p, c_char_p] |
|---|
| 558 | def opengroup(self, name, nxclass=None): |
|---|
| 559 | """ |
|---|
| 560 | Open the group nxclass:name. If the nxclass is not specified |
|---|
| 561 | this will search for it. |
|---|
| 562 | |
|---|
| 563 | Raises NeXusError if the group could not be opened. |
|---|
| 564 | |
|---|
| 565 | Corresponds to NXopengroup(handle, name, nxclass) |
|---|
| 566 | """ |
|---|
| 567 | #print "opengroup",self._loc(),name,nxclass |
|---|
| 568 | if nxclass is None: |
|---|
| 569 | nxclass = self.__getnxclass(name) |
|---|
| 570 | status = nxlib.nxiopengroup_(self.handle, name, nxclass) |
|---|
| 571 | if status == ERROR: |
|---|
| 572 | raise ValueError,\ |
|---|
| 573 | "Could not open %s:%s in %s"%(nxclass,name,self._loc()) |
|---|
| 574 | self._path.append((name,nxclass)) |
|---|
| 575 | |
|---|
| 576 | nxlib.nxiclosegroup_.restype = c_int |
|---|
| 577 | nxlib.nxiclosegroup_.argtypes = [c_void_p] |
|---|
| 578 | def closegroup(self): |
|---|
| 579 | """ |
|---|
| 580 | Close the currently open group. |
|---|
| 581 | |
|---|
| 582 | Raises NeXusError if the group could not be closed. |
|---|
| 583 | |
|---|
| 584 | Corresponds to NXclosegroup(handle) |
|---|
| 585 | """ |
|---|
| 586 | #print "closegroup" |
|---|
| 587 | if self._indata: |
|---|
| 588 | raise NeXusError, "Close data before group at %s"%(self._loc()) |
|---|
| 589 | status = nxlib.nxiclosegroup_(self.handle) |
|---|
| 590 | if status == ERROR: |
|---|
| 591 | raise NeXusError, "Could not close group at %s"%(self._loc()) |
|---|
| 592 | self._path.pop() |
|---|
| 593 | |
|---|
| 594 | nxlib.nxigetgroupinfo_.restype = c_int |
|---|
| 595 | nxlib.nxigetgroupinfo_.argtypes = [c_void_p, c_int_p, c_char_p, c_char_p] |
|---|
| 596 | def getgroupinfo(self): |
|---|
| 597 | """ |
|---|
| 598 | Query the currently open group returning the tuple |
|---|
| 599 | numentries, name, nxclass. |
|---|
| 600 | |
|---|
| 601 | Raises ValueError if the group could not be opened. |
|---|
| 602 | |
|---|
| 603 | Corresponds to NXgetgroupinfo(handle) |
|---|
| 604 | |
|---|
| 605 | Note: corrects error in HDF5 where getgroupinfo returns the entire |
|---|
| 606 | path rather than the group name. Use the path attribute to get |
|---|
| 607 | a sensible value of path. |
|---|
| 608 | """ |
|---|
| 609 | # Space for the returned strings |
|---|
| 610 | path = ctypes.create_string_buffer(MAXPATHLEN) |
|---|
| 611 | nxclass = ctypes.create_string_buffer(MAXNAMELEN) |
|---|
| 612 | n = c_int(0) |
|---|
| 613 | status = nxlib.nxigetgroupinfo_(self.handle,_ref(n),path,nxclass) |
|---|
| 614 | if status == ERROR: |
|---|
| 615 | raise ValueError, "Could not get group info: %s"%(self._loc()) |
|---|
| 616 | #print "getgroupinfo",self._loc(),nxclass.value,name.value,n.value |
|---|
| 617 | name = path.value.split('/')[-1] # Protect against HDF5 returning path |
|---|
| 618 | return n.value,name,nxclass.value |
|---|
| 619 | |
|---|
| 620 | nxlib.nxiinitgroupdir_.restype = c_int |
|---|
| 621 | nxlib.nxiinitgroupdir_.argtypes = [c_void_p] |
|---|
| 622 | def initgroupdir(self): |
|---|
| 623 | """ |
|---|
| 624 | Reset getnextentry to return the first entry in the group. |
|---|
| 625 | |
|---|
| 626 | Raises NeXusError if this fails. |
|---|
| 627 | |
|---|
| 628 | Corresponds to NXinitgroupdir(handle) |
|---|
| 629 | """ |
|---|
| 630 | status = nxlib.nxiinitgroupdir_(self.handle) |
|---|
| 631 | if status == ERROR: |
|---|
| 632 | raise NeXusError, \ |
|---|
| 633 | "Could not reset group scan: %s"%(self._loc()) |
|---|
| 634 | |
|---|
| 635 | nxlib.nxigetnextentry_.restype = c_int |
|---|
| 636 | nxlib.nxigetnextentry_.argtypes = [c_void_p, c_char_p, c_char_p, c_int_p] |
|---|
| 637 | def getnextentry(self): |
|---|
| 638 | """ |
|---|
| 639 | Return the next entry in the group as name,nxclass tuple. If |
|---|
| 640 | end of data is reached this returns the tuple (None, None) |
|---|
| 641 | |
|---|
| 642 | Raises NeXusError if this fails. |
|---|
| 643 | |
|---|
| 644 | Corresponds to NXgetnextentry(handle,name,nxclass,&storage). |
|---|
| 645 | |
|---|
| 646 | This function doesn't return the storage class for data entries |
|---|
| 647 | since getinfo returns shape and storage, both of which are required |
|---|
| 648 | to read the data. |
|---|
| 649 | |
|---|
| 650 | Note that HDF4 files can have entries in the file with classes |
|---|
| 651 | that don't need to be processed. If the file follows the standard |
|---|
| 652 | NeXus DTDs then skip any entry for which nxclass.startswith('NX') |
|---|
| 653 | is False. For non-conforming files, skip those entries with |
|---|
| 654 | nxclass in nxs.H4SKIP. |
|---|
| 655 | """ |
|---|
| 656 | name = ctypes.create_string_buffer(MAXNAMELEN) |
|---|
| 657 | nxclass = ctypes.create_string_buffer(MAXNAMELEN) |
|---|
| 658 | storage = c_int(0) |
|---|
| 659 | status = nxlib.nxigetnextentry_(self.handle,name,nxclass,_ref(storage)) |
|---|
| 660 | if status == EOD: |
|---|
| 661 | return (None, None) |
|---|
| 662 | if status == ERROR: |
|---|
| 663 | raise NeXusError, \ |
|---|
| 664 | "Could not get next entry: %s"%(self._loc()) |
|---|
| 665 | ## Note: ignoring storage --- it is useless without dimensions |
|---|
| 666 | #if nxclass == 'SDS': |
|---|
| 667 | # dtype = _pytype_code(storage.value) |
|---|
| 668 | #print "nextentry",nxclass.value, name.value, storage.value |
|---|
| 669 | return name.value,nxclass.value |
|---|
| 670 | |
|---|
| 671 | def getentries(self): |
|---|
| 672 | """ |
|---|
| 673 | Return a dictionary of the groups[name]=type below the |
|---|
| 674 | existing open one. |
|---|
| 675 | |
|---|
| 676 | Raises NeXusError if this fails. |
|---|
| 677 | """ |
|---|
| 678 | self.initgroupdir() |
|---|
| 679 | result = {} |
|---|
| 680 | (name, nxclass) = self.getnextentry() |
|---|
| 681 | if (name, nxclass) != (None, None): |
|---|
| 682 | result[name] = nxclass |
|---|
| 683 | while (name, nxclass) != (None, None): |
|---|
| 684 | result[name] = nxclass |
|---|
| 685 | (name, nxclass) = self.getnextentry() |
|---|
| 686 | return result |
|---|
| 687 | |
|---|
| 688 | def __getnxclass(self, target): |
|---|
| 689 | """ |
|---|
| 690 | Return the nxclass of the supplied name. |
|---|
| 691 | """ |
|---|
| 692 | self.initgroupdir() |
|---|
| 693 | while True: |
|---|
| 694 | (nxname, nxclass) = self.getnextentry() |
|---|
| 695 | if nxname == target: |
|---|
| 696 | return nxclass |
|---|
| 697 | if nxname is None: |
|---|
| 698 | break |
|---|
| 699 | raise NeXusError("Failed to find entry with name \"%s\" at %s" % (target, self.path)) |
|---|
| 700 | |
|---|
| 701 | def entries(self): |
|---|
| 702 | """ |
|---|
| 703 | Iterator of entries. |
|---|
| 704 | |
|---|
| 705 | for name,nxclass in nxs.entries(): |
|---|
| 706 | process(name,nxclass) |
|---|
| 707 | |
|---|
| 708 | This automatically opens the corresponding group/data for you, |
|---|
| 709 | and closes it when you are done. Do not rely on any paths |
|---|
| 710 | remaining open between entries as we restore the current |
|---|
| 711 | path each time. |
|---|
| 712 | |
|---|
| 713 | This does not correspond to an existing NeXus API function, |
|---|
| 714 | but instead combines the work of initgroupdir/getnextentry |
|---|
| 715 | and open/close on data and group. Entries in nxs.H4SKIP are |
|---|
| 716 | ignored. |
|---|
| 717 | """ |
|---|
| 718 | # To preserve the semantics we must read in the whole list |
|---|
| 719 | # first, then process the entries one by one. Keep track |
|---|
| 720 | # of the path so we can restore it between entries. |
|---|
| 721 | path = self.path |
|---|
| 722 | |
|---|
| 723 | # Read list of entries |
|---|
| 724 | self.initgroupdir() |
|---|
| 725 | n,_,_ = self.getgroupinfo() |
|---|
| 726 | L = [] |
|---|
| 727 | for dummy in range(n): |
|---|
| 728 | name,nxclass = self.getnextentry() |
|---|
| 729 | if nxclass not in H4SKIP: |
|---|
| 730 | L.append((name,nxclass)) |
|---|
| 731 | for name,nxclass in L: |
|---|
| 732 | self.openpath(path) # Reset the file cursor |
|---|
| 733 | if nxclass == "SDS": |
|---|
| 734 | self.opendata(name) |
|---|
| 735 | else: |
|---|
| 736 | self.opengroup(name,nxclass) |
|---|
| 737 | yield name,nxclass |
|---|
| 738 | |
|---|
| 739 | # ==== Data ==== |
|---|
| 740 | nxlib.nxigetrawinfo64_.restype = c_int |
|---|
| 741 | nxlib.nxigetrawinfo64_.argtypes = [c_void_p, c_int_p, c_void_p, c_int_p] |
|---|
| 742 | def getrawinfo(self): |
|---|
| 743 | """ |
|---|
| 744 | Returns the tuple dimensions,type for the currently open dataset. |
|---|
| 745 | Dimensions is an integer array whose length corresponds to the rank |
|---|
| 746 | of the dataset and whose elements are the size of the individual |
|---|
| 747 | dimensions. Storage type is returned as a string, with 'char' for |
|---|
| 748 | a stored string, '[u]int[8|16|32]' for various integer values or |
|---|
| 749 | 'float[32|64]' for floating point values. No support for |
|---|
| 750 | complex values. |
|---|
| 751 | |
|---|
| 752 | Unlike getinfo(), the size of the string storage area is |
|---|
| 753 | returned rather than the length of the stored string. |
|---|
| 754 | |
|---|
| 755 | Raises NeXusError if this fails. |
|---|
| 756 | |
|---|
| 757 | Corresponds to NXgetrawinfo(handle, &rank, dims, &storage), |
|---|
| 758 | but with storage converted from HDF values to numpy compatible |
|---|
| 759 | strings, and rank implicit in the length of the returned dimensions. |
|---|
| 760 | """ |
|---|
| 761 | rank = c_int(0) |
|---|
| 762 | shape = numpy.zeros(MAXRANK, 'int64') |
|---|
| 763 | storage = c_int(0) |
|---|
| 764 | status = nxlib.nxigetrawinfo64_(self.handle, _ref(rank), |
|---|
| 765 | shape.ctypes.data, _ref(storage)) |
|---|
| 766 | if status == ERROR: |
|---|
| 767 | raise NeXusError, "Could not get data info: %s"%(self._loc()) |
|---|
| 768 | shape = shape[:rank.value]+0 |
|---|
| 769 | dtype = _pytype_code[storage.value] |
|---|
| 770 | #print "getrawinfo",self._loc(),"->",shape,dtype |
|---|
| 771 | return shape,dtype |
|---|
| 772 | |
|---|
| 773 | nxlib.nxigetinfo64_.restype = c_int |
|---|
| 774 | nxlib.nxigetinfo64_.argtypes = [c_void_p, c_int_p, c_void_p, c_int_p] |
|---|
| 775 | def getinfo(self): |
|---|
| 776 | """ |
|---|
| 777 | Returns the tuple dimensions,type for the currently open dataset. |
|---|
| 778 | Dimensions is an integer array whose length corresponds to the rank |
|---|
| 779 | of the dataset and whose elements are the size of the individual |
|---|
| 780 | dimensions. Storage type is returned as a string, with 'char' for |
|---|
| 781 | a stored string, '[u]int[8|16|32]' for various integer values or |
|---|
| 782 | 'float[32|64]' for floating point values. No support for |
|---|
| 783 | complex values. |
|---|
| 784 | |
|---|
| 785 | Unlike getrawinfo(), the length of the stored string is |
|---|
| 786 | returned rather than the size of the string storage area. |
|---|
| 787 | |
|---|
| 788 | Raises NeXusError if this fails. |
|---|
| 789 | |
|---|
| 790 | Note that this is the recommended way to establish if you have |
|---|
| 791 | a dataset open. |
|---|
| 792 | |
|---|
| 793 | Corresponds to NXgetinfo(handle, &rank, dims, &storage), |
|---|
| 794 | but with storage converted from HDF values to numpy compatible |
|---|
| 795 | strings, and rank implicit in the length of the returned dimensions. |
|---|
| 796 | """ |
|---|
| 797 | rank = c_int(0) |
|---|
| 798 | shape = numpy.zeros(MAXRANK, 'int64') |
|---|
| 799 | storage = c_int(0) |
|---|
| 800 | status = nxlib.nxigetinfo64_(self.handle, _ref(rank), |
|---|
| 801 | shape.ctypes.data, |
|---|
| 802 | _ref(storage)) |
|---|
| 803 | if status == ERROR: |
|---|
| 804 | raise NeXusError, "Could not get data info: %s"%(self._loc()) |
|---|
| 805 | shape = shape[:rank.value]+0 |
|---|
| 806 | dtype = _pytype_code[storage.value] |
|---|
| 807 | #print "getinfo",self._loc(),"->",shape,dtype |
|---|
| 808 | return shape,dtype |
|---|
| 809 | |
|---|
| 810 | nxlib.nxiopendata_.restype = c_int |
|---|
| 811 | nxlib.nxiopendata_.argtypes = [c_void_p, c_char_p] |
|---|
| 812 | def opendata(self, name): |
|---|
| 813 | """ |
|---|
| 814 | Open the named data set within the current group. |
|---|
| 815 | |
|---|
| 816 | Raises ValueError if could not open the dataset. |
|---|
| 817 | |
|---|
| 818 | Corresponds to NXopendata(handle, name) |
|---|
| 819 | """ |
|---|
| 820 | #print "opendata",self._loc(),name |
|---|
| 821 | if self._indata: |
|---|
| 822 | status = ERROR |
|---|
| 823 | else: |
|---|
| 824 | status = nxlib.nxiopendata_(self.handle, name) |
|---|
| 825 | if status == ERROR: |
|---|
| 826 | raise ValueError, "Could not open data %s: %s"%(name, self._loc()) |
|---|
| 827 | self._path.append((name,"SDS")) |
|---|
| 828 | self._indata = True |
|---|
| 829 | |
|---|
| 830 | nxlib.nxiclosedata_.restype = c_int |
|---|
| 831 | nxlib.nxiclosedata_.argtypes = [c_void_p] |
|---|
| 832 | def closedata(self): |
|---|
| 833 | """ |
|---|
| 834 | Close the currently open data set. |
|---|
| 835 | |
|---|
| 836 | Raises NeXusError if this fails (e.g., because no |
|---|
| 837 | dataset is open). |
|---|
| 838 | |
|---|
| 839 | Corresponds to NXclosedata(handle) |
|---|
| 840 | """ |
|---|
| 841 | #print "closedata" |
|---|
| 842 | status = nxlib.nxiclosedata_(self.handle) |
|---|
| 843 | if status == ERROR: |
|---|
| 844 | raise NeXusError,\ |
|---|
| 845 | "Could not close data at %s"%(self._loc()) |
|---|
| 846 | self._path.pop() |
|---|
| 847 | self._indata = False |
|---|
| 848 | |
|---|
| 849 | nxlib.nximakedata64_.restype = c_int |
|---|
| 850 | nxlib.nximakedata64_.argtypes = [c_void_p, c_char_p, c_int, c_int, c_int64_p] |
|---|
| 851 | def makedata(self, name, dtype=None, shape=None): |
|---|
| 852 | """ |
|---|
| 853 | Create a data element of the given type and shape. See getinfo |
|---|
| 854 | for details on types. This does not open the data for writing. |
|---|
| 855 | |
|---|
| 856 | Set the first dimension to nxs.UNLIMITED, for extensible data sets, |
|---|
| 857 | and use putslab to write individual slabs. |
|---|
| 858 | |
|---|
| 859 | Raises ValueError if it fails. |
|---|
| 860 | |
|---|
| 861 | Corresponds to NXmakedata(handle,name,type,rank,dims) |
|---|
| 862 | """ |
|---|
| 863 | # TODO: With keywords for compression and chunks, this can act as |
|---|
| 864 | # TODO: compmakedata. |
|---|
| 865 | # TODO: With keywords for value and attr, this can be used for |
|---|
| 866 | # TODO: makedata, opendata, putdata, putattr, putattr, ..., closedata |
|---|
| 867 | #print "makedata",self._loc(),name,shape,dtype |
|---|
| 868 | storage = _nxtype_code[str(dtype)] |
|---|
| 869 | shape = numpy.asarray(shape,'int64') |
|---|
| 870 | status = nxlib.nximakedata64_(self.handle,name,storage,len(shape), |
|---|
| 871 | shape.ctypes.data_as(c_int64_p)) |
|---|
| 872 | if status == ERROR: |
|---|
| 873 | raise ValueError, "Could not create data %s: %s"%(name,self._loc()) |
|---|
| 874 | |
|---|
| 875 | nxlib.nxicompmakedata64_.restype = c_int |
|---|
| 876 | nxlib.nxicompmakedata64_.argtypes = [c_void_p, c_char_p, c_int, c_int, c_int64_p, |
|---|
| 877 | c_int, c_int64_p] |
|---|
| 878 | def compmakedata(self, name, dtype=None, shape=None, mode='lzw', |
|---|
| 879 | chunks=None): |
|---|
| 880 | """ |
|---|
| 881 | Create a data element of the given dimensions and type. See |
|---|
| 882 | getinfo for details on types. Compression mode is one of |
|---|
| 883 | 'none', 'lzw', 'rle' or 'huffman'. chunks gives the alignment |
|---|
| 884 | of the compressed chunks in the data file. There should be one |
|---|
| 885 | chunk size for each dimension in the data. |
|---|
| 886 | |
|---|
| 887 | Defaults to mode='lzw' with chunk size set to the length of the |
|---|
| 888 | fastest varying dimension. |
|---|
| 889 | |
|---|
| 890 | Raises ValueError if it fails. |
|---|
| 891 | |
|---|
| 892 | Corresponds to NXmakedata(handle,name,type,rank,dims). |
|---|
| 893 | """ |
|---|
| 894 | storage = _nxtype_code[str(dtype)] |
|---|
| 895 | # Make sure shape/chunk_shape are integers; hope that 32/64 bit issues |
|---|
| 896 | # with the c int type sort themselves out. |
|---|
| 897 | dims = numpy.asarray(shape,'int64') |
|---|
| 898 | if chunks == None: |
|---|
| 899 | chunks = numpy.ones(dims.shape,'int64') |
|---|
| 900 | chunks[-1] = shape[-1] |
|---|
| 901 | else: |
|---|
| 902 | chunks = numpy.array(chunks,'int64') |
|---|
| 903 | status = nxlib.nxicompmakedata64_(self.handle,name,storage,len(dims), |
|---|
| 904 | dims.ctypes.data_as(c_int64_p), |
|---|
| 905 | _compression_code[mode], |
|---|
| 906 | chunks.ctypes.data_as(c_int64_p)) |
|---|
| 907 | if status == ERROR: |
|---|
| 908 | raise ValueError, \ |
|---|
| 909 | "Could not create compressed data %s: %s"%(name,self._loc()) |
|---|
| 910 | |
|---|
| 911 | nxlib.nxigetdata_.restype = c_int |
|---|
| 912 | nxlib.nxigetdata_.argtypes = [c_void_p, c_void_p] |
|---|
| 913 | def getdata(self): |
|---|
| 914 | """ |
|---|
| 915 | Return the data. If data is a string (1-D char array), a python |
|---|
| 916 | string is returned. If data is a scalar (1-D numeric array of |
|---|
| 917 | length 1), a python scalar is returned. If data is a string |
|---|
| 918 | array, a numpy array of type 'S#' where # is the maximum string |
|---|
| 919 | length is returned. If data is a numeric array, a numpy array |
|---|
| 920 | is returned. |
|---|
| 921 | |
|---|
| 922 | Raises ValueError if this fails. |
|---|
| 923 | |
|---|
| 924 | Corresponds to NXgetdata(handle, data) |
|---|
| 925 | """ |
|---|
| 926 | # TODO: consider accepting preallocated data so we don't thrash memory |
|---|
| 927 | shape,dtype = self.getinfo() |
|---|
| 928 | dummy_data,pdata,dummy_size,datafn = self._poutput(dtype,shape) |
|---|
| 929 | status = nxlib.nxigetdata_(self.handle,pdata) |
|---|
| 930 | if status == ERROR: |
|---|
| 931 | raise ValueError, "Could not read data: %s"%(self._loc()) |
|---|
| 932 | #print "getdata",self._loc(),shape,dtype |
|---|
| 933 | return datafn() |
|---|
| 934 | |
|---|
| 935 | nxlib.nxigetslab64_.restype = c_int |
|---|
| 936 | nxlib.nxigetslab64_.argtypes = [c_void_p, c_void_p, c_int64_p, c_int64_p] |
|---|
| 937 | def getslab(self, slab_offset, slab_shape): |
|---|
| 938 | """ |
|---|
| 939 | Get a slab from the data array. |
|---|
| 940 | |
|---|
| 941 | Offsets are 0-origin. Shape can be inferred from the data. |
|---|
| 942 | Offset and shape must each have one entry per dimension. |
|---|
| 943 | |
|---|
| 944 | Raises ValueError if this fails. |
|---|
| 945 | |
|---|
| 946 | Corresponds to NXgetslab(handle,data,offset,shape) |
|---|
| 947 | """ |
|---|
| 948 | # TODO: consider accepting preallocated data so we don't thrash memory |
|---|
| 949 | dummy_shape,dtype = self.getrawinfo() |
|---|
| 950 | dummy_data,pdata,dummy_size,datafn = self._poutput(dtype,slab_shape) |
|---|
| 951 | slab_offset = numpy.asarray(slab_offset,'int64') |
|---|
| 952 | slab_shape = numpy.asarray(slab_shape,'int64') |
|---|
| 953 | status = nxlib.nxigetslab64_(self.handle,pdata, |
|---|
| 954 | slab_offset.ctypes.data_as(c_int64_p), |
|---|
| 955 | slab_shape.ctypes.data_as(c_int64_p)) |
|---|
| 956 | #print "slab",offset,size,data |
|---|
| 957 | if status == ERROR: |
|---|
| 958 | raise ValueError, "Could not read slab: %s"%(self._loc()) |
|---|
| 959 | return datafn() |
|---|
| 960 | |
|---|
| 961 | nxlib.nxiputdata_.restype = c_int |
|---|
| 962 | nxlib.nxiputdata_.argtypes = [c_void_p, c_void_p] |
|---|
| 963 | def putdata(self, data): |
|---|
| 964 | """ |
|---|
| 965 | Write data into the currently open data block. |
|---|
| 966 | |
|---|
| 967 | Raises ValueError if this fails. |
|---|
| 968 | |
|---|
| 969 | Corresponds to NXputdata(handle, data) |
|---|
| 970 | """ |
|---|
| 971 | shape,dtype = self.getrawinfo() |
|---|
| 972 | #print "putdata",self._loc(),shape,dtype |
|---|
| 973 | data,pdata = self._pinput(data,dtype,shape) |
|---|
| 974 | status = nxlib.nxiputdata_(self.handle,pdata) |
|---|
| 975 | if status == ERROR: |
|---|
| 976 | raise ValueError, "Could not write data: %s"%(self._loc()) |
|---|
| 977 | |
|---|
| 978 | nxlib.nxiputslab64_.restype = c_int |
|---|
| 979 | nxlib.nxiputslab64_.argtypes = [c_void_p, c_void_p, c_int64_p, c_int64_p] |
|---|
| 980 | def putslab(self, data, slab_offset, slab_shape): |
|---|
| 981 | """ |
|---|
| 982 | Put a slab into the data array. |
|---|
| 983 | |
|---|
| 984 | Offsets are 0-origin. Shape can be inferred from the data. |
|---|
| 985 | Offset and shape must each have one entry per dimension. |
|---|
| 986 | |
|---|
| 987 | Raises ValueError if this fails. |
|---|
| 988 | |
|---|
| 989 | Corresponds to NXputslab(handle,data,offset,shape) |
|---|
| 990 | """ |
|---|
| 991 | dummy_shape,dtype = self.getrawinfo() |
|---|
| 992 | data,pdata = self._pinput(data,dtype,slab_shape) |
|---|
| 993 | slab_offset = numpy.asarray(slab_offset,'int64') |
|---|
| 994 | slab_shape = numpy.asarray(slab_shape,'int64') |
|---|
| 995 | #print "slab",offset,size,data |
|---|
| 996 | status = nxlib.nxiputslab64_(self.handle,pdata, |
|---|
| 997 | slab_offset.ctypes.data_as(c_int64_p), |
|---|
| 998 | slab_shape.ctypes.data_as(c_int64_p)) |
|---|
| 999 | if status == ERROR: |
|---|
| 1000 | raise ValueError, "Could not write slab: %s"%(self._loc()) |
|---|
| 1001 | |
|---|
| 1002 | |
|---|
| 1003 | |
|---|
| 1004 | # ==== Attributes ==== |
|---|
| 1005 | nxlib.nxiinitattrdir_.restype = c_int |
|---|
| 1006 | nxlib.nxiinitattrdir_.argtypes = [c_void_p] |
|---|
| 1007 | def initattrdir(self): |
|---|
| 1008 | """ |
|---|
| 1009 | Reset the getnextattr list to the first attribute. |
|---|
| 1010 | |
|---|
| 1011 | Raises NeXusError if this fails. |
|---|
| 1012 | |
|---|
| 1013 | Corresponds to NXinitattrdir(handle) |
|---|
| 1014 | """ |
|---|
| 1015 | status = nxlib.nxiinitattrdir_(self.handle) |
|---|
| 1016 | if status == ERROR: |
|---|
| 1017 | raise NeXusError, \ |
|---|
| 1018 | "Could not reset attribute list: %s"%(self._loc()) |
|---|
| 1019 | |
|---|
| 1020 | nxlib.nxigetattrinfo_.restype = c_int |
|---|
| 1021 | nxlib.nxigetattrinfo_.argtypes = [c_void_p, c_int_p] |
|---|
| 1022 | def getattrinfo(self): |
|---|
| 1023 | """ |
|---|
| 1024 | Returns the number of attributes for the currently open |
|---|
| 1025 | group/data object. Do not call getnextattr() more than |
|---|
| 1026 | this number of times. |
|---|
| 1027 | |
|---|
| 1028 | Raises NeXusError if this fails. |
|---|
| 1029 | |
|---|
| 1030 | Corresponds to NXgetattrinfo(handl, &n) |
|---|
| 1031 | """ |
|---|
| 1032 | n = c_int(0) |
|---|
| 1033 | status = nxlib.nxigetattrinfo_(self.handle,_ref(n)) |
|---|
| 1034 | if status == ERROR: |
|---|
| 1035 | raise NeXusError, "Could not get attr info: %s"%(self._loc()) |
|---|
| 1036 | #print "num attrs",n.value |
|---|
| 1037 | return n.value |
|---|
| 1038 | |
|---|
| 1039 | nxlib.nxigetnextattr_.restype = c_int |
|---|
| 1040 | nxlib.nxigetnextattr_.argtypes = [c_void_p, c_char_p, c_int_p, c_int_p] |
|---|
| 1041 | def getnextattr(self): |
|---|
| 1042 | """ |
|---|
| 1043 | Returns the name, length, and data type for the next attribute. |
|---|
| 1044 | Call getattrinfo to determine the number of attributes before |
|---|
| 1045 | calling getnextattr. Data type is returned as a string. See |
|---|
| 1046 | getinfo for details. Length is the number of elements in the |
|---|
| 1047 | attribute. |
|---|
| 1048 | |
|---|
| 1049 | Raises NeXusError if NeXus returns ERROR or EOD. |
|---|
| 1050 | |
|---|
| 1051 | Corresponds to NXgetnextattr(handle,name,&length,&storage) |
|---|
| 1052 | but with storage converted from HDF values to numpy compatible |
|---|
| 1053 | strings. |
|---|
| 1054 | |
|---|
| 1055 | Note: NeXus API documentation seems to say that length is the number |
|---|
| 1056 | of bytes required to store the entire attribute. |
|---|
| 1057 | """ |
|---|
| 1058 | name = ctypes.create_string_buffer(MAXNAMELEN) |
|---|
| 1059 | length = c_int(0) |
|---|
| 1060 | storage = c_int(0) |
|---|
| 1061 | status = nxlib.nxigetnextattr_(self.handle,name,_ref(length),_ref(storage)) |
|---|
| 1062 | if status == EOD: |
|---|
| 1063 | return (None, None, None) |
|---|
| 1064 | if status == ERROR or status == EOD: |
|---|
| 1065 | raise NeXusError, "Could not get next attr: %s"%(self._loc()) |
|---|
| 1066 | dtype = _pytype_code[storage.value] |
|---|
| 1067 | #print "getnextattr",name.value,length.value,dtype |
|---|
| 1068 | return name.value, length.value, dtype |
|---|
| 1069 | |
|---|
| 1070 | # TODO: Resolve discrepency between NeXus API documentation and |
|---|
| 1071 | # TODO: apparent behaviour for getattr/putattr length. |
|---|
| 1072 | nxlib.nxigetattr_.restype = c_int |
|---|
| 1073 | nxlib.nxigetattr_.argtypes = [c_void_p, c_char_p, c_void_p, c_int_p, c_int_p] |
|---|
| 1074 | def getattr(self, name, length, dtype): |
|---|
| 1075 | """ |
|---|
| 1076 | Returns the value of the named attribute. Requires length and |
|---|
| 1077 | data type from getnextattr to allocate the appropriate amount of |
|---|
| 1078 | space for the attribute. |
|---|
| 1079 | |
|---|
| 1080 | Corresponds to NXgetattr(handle,name,data,&length,&storage) |
|---|
| 1081 | """ |
|---|
| 1082 | if dtype is 'char': length += 1 # HDF4 needs zero-terminator |
|---|
| 1083 | dummy_data,pdata,size,datafn = self._poutput(str(dtype),[length]) |
|---|
| 1084 | storage = c_int(_nxtype_code[str(dtype)]) |
|---|
| 1085 | #print "getattr",self._loc(),name,length,size,dtype |
|---|
| 1086 | size = c_int(size) |
|---|
| 1087 | status = nxlib.nxigetattr_(self.handle,name,pdata,_ref(size),_ref(storage)) |
|---|
| 1088 | if status == ERROR: |
|---|
| 1089 | raise ValueError, "Could not read attr %s: %s" % (name,self._loc()) |
|---|
| 1090 | #print "getattr",self._loc(),name,datafn() |
|---|
| 1091 | return datafn() |
|---|
| 1092 | |
|---|
| 1093 | nxlib.nxiputattr_.restype = c_int |
|---|
| 1094 | nxlib.nxiputattr_.argtypes = [c_void_p, c_char_p, c_void_p, c_int, c_int] |
|---|
| 1095 | def putattr(self, name, value, dtype = None): |
|---|
| 1096 | """ |
|---|
| 1097 | Saves the named attribute. The attribute value is a string |
|---|
| 1098 | or a scalar. |
|---|
| 1099 | |
|---|
| 1100 | Raises TypeError if the value type is incorrect. |
|---|
| 1101 | Raises NeXusError if the attribute could not be saved. |
|---|
| 1102 | |
|---|
| 1103 | Corresponds to NXputattr(handle,name,data,length,storage) |
|---|
| 1104 | |
|---|
| 1105 | Note length is the number of elements to write rather |
|---|
| 1106 | than the number of bytes to write. |
|---|
| 1107 | """ |
|---|
| 1108 | # Establish attribute type |
|---|
| 1109 | if dtype == None: |
|---|
| 1110 | # Type is inferred from value |
|---|
| 1111 | if hasattr(value,'dtype'): |
|---|
| 1112 | dtype = str(value.dtype) |
|---|
| 1113 | elif _is_string_like(value): |
|---|
| 1114 | dtype = 'char' |
|---|
| 1115 | else: |
|---|
| 1116 | value = numpy.array(value) |
|---|
| 1117 | dtype = str(value.dtype) |
|---|
| 1118 | else: |
|---|
| 1119 | # Set value to type |
|---|
| 1120 | dtype = str(dtype) |
|---|
| 1121 | if dtype == 'char' and not _is_string_like(value): |
|---|
| 1122 | raise TypeError, "Expected string for 'char' attribute value" |
|---|
| 1123 | if dtype != 'char': |
|---|
| 1124 | value = numpy.array(value,dtype=dtype) |
|---|
| 1125 | |
|---|
| 1126 | # Determine shape |
|---|
| 1127 | if dtype == 'char': |
|---|
| 1128 | length = len(value) |
|---|
| 1129 | data = value |
|---|
| 1130 | elif numpy.prod(value.shape) != 1: |
|---|
| 1131 | # NAPI silently ignores attribute arrays |
|---|
| 1132 | raise TypeError, "Attribute value must be scalar or string" |
|---|
| 1133 | else: |
|---|
| 1134 | length = 1 |
|---|
| 1135 | data = value.ctypes.data |
|---|
| 1136 | |
|---|
| 1137 | # Perform the call |
|---|
| 1138 | storage = c_int(_nxtype_code[dtype]) |
|---|
| 1139 | status = nxlib.nxiputattr_(self.handle,name,data,length,storage) |
|---|
| 1140 | if status == ERROR: |
|---|
| 1141 | raise NeXusError, "Could not write attr %s: %s"%(name,self._loc()) |
|---|
| 1142 | |
|---|
| 1143 | def getattrs(self): |
|---|
| 1144 | """ |
|---|
| 1145 | Returns a dicitonary of the attributes on the current node. |
|---|
| 1146 | |
|---|
| 1147 | This is a second form of attrs(self). |
|---|
| 1148 | """ |
|---|
| 1149 | result = {} |
|---|
| 1150 | for (name, value) in self.attrs(): |
|---|
| 1151 | result[name] = value |
|---|
| 1152 | return result |
|---|
| 1153 | |
|---|
| 1154 | def attrs(self): |
|---|
| 1155 | """ |
|---|
| 1156 | Iterate over attributes. |
|---|
| 1157 | |
|---|
| 1158 | for name,value in file.attrs(): |
|---|
| 1159 | process(name,value) |
|---|
| 1160 | |
|---|
| 1161 | This automatically reads the attributes of the group/data. Do not |
|---|
| 1162 | change the active group/data while processing the list. |
|---|
| 1163 | |
|---|
| 1164 | This does not correspond to an existing NeXus API function, but |
|---|
| 1165 | combines the work of attrinfo/initattrdir/getnextattr/getattr. |
|---|
| 1166 | """ |
|---|
| 1167 | self.initattrdir() |
|---|
| 1168 | n = self.getattrinfo() |
|---|
| 1169 | for dummy in range(n): |
|---|
| 1170 | name,length,dtype = self.getnextattr() |
|---|
| 1171 | value = self.getattr(name,length,dtype) |
|---|
| 1172 | yield name,value |
|---|
| 1173 | |
|---|
| 1174 | # ==== Linking ==== |
|---|
| 1175 | nxlib.nxigetgroupid_.restype = c_int |
|---|
| 1176 | nxlib.nxigetgroupid_.argtypes = [c_void_p, c_NXlink_p] |
|---|
| 1177 | def getgroupID(self): |
|---|
| 1178 | """ |
|---|
| 1179 | Return the id of the current group so we can link to it later. |
|---|
| 1180 | |
|---|
| 1181 | Raises NeXusError |
|---|
| 1182 | |
|---|
| 1183 | Corresponds to NXgetgroupID(handle, &ID) |
|---|
| 1184 | """ |
|---|
| 1185 | ID = _NXlink() |
|---|
| 1186 | status = nxlib.nxigetgroupid_(self.handle,_ref(ID)) |
|---|
| 1187 | if status == ERROR: |
|---|
| 1188 | raise NeXusError, "Could not link to group: %s"%(self._loc()) |
|---|
| 1189 | return ID |
|---|
| 1190 | |
|---|
| 1191 | nxlib.nxigetdataid_.restype = c_int |
|---|
| 1192 | nxlib.nxigetdataid_.argtypes = [c_void_p, c_NXlink_p] |
|---|
| 1193 | def getdataID(self): |
|---|
| 1194 | """ |
|---|
| 1195 | Return the id of the current data so we can link to it later. |
|---|
| 1196 | |
|---|
| 1197 | Raises NeXusError |
|---|
| 1198 | |
|---|
| 1199 | Corresponds to NXgetdataID(handle, &ID) |
|---|
| 1200 | """ |
|---|
| 1201 | ID = _NXlink() |
|---|
| 1202 | status = nxlib.nxigetdataid_(self.handle,_ref(ID)) |
|---|
| 1203 | if status == ERROR: |
|---|
| 1204 | raise NeXusError, "Could not link to data: %s"%(self._loc()) |
|---|
| 1205 | return ID |
|---|
| 1206 | |
|---|
| 1207 | nxlib.nximakelink_.restype = c_int |
|---|
| 1208 | nxlib.nximakelink_.argtypes = [c_void_p, c_NXlink_p] |
|---|
| 1209 | def makelink(self, ID): |
|---|
| 1210 | """ |
|---|
| 1211 | Link the previously captured group/data ID into the currently |
|---|
| 1212 | open group. |
|---|
| 1213 | |
|---|
| 1214 | Raises NeXusError |
|---|
| 1215 | |
|---|
| 1216 | Corresponds to NXmakelink(handle, &ID) |
|---|
| 1217 | """ |
|---|
| 1218 | status = nxlib.nximakelink_(self.handle,_ref(ID)) |
|---|
| 1219 | if status == ERROR: |
|---|
| 1220 | raise NeXusError, "Could not make link: %s"%(self._loc()) |
|---|
| 1221 | |
|---|
| 1222 | nxlib.nximakenamedlink_.restype = c_int |
|---|
| 1223 | nxlib.nximakenamedlink_.argtypes = [c_void_p, c_char_p, c_NXlink_p] |
|---|
| 1224 | def makenamedlink(self,name,ID): |
|---|
| 1225 | """ |
|---|
| 1226 | Link the previously captured group/data ID into the currently |
|---|
| 1227 | open group, but under a different name. |
|---|
| 1228 | |
|---|
| 1229 | Raises NeXusError |
|---|
| 1230 | |
|---|
| 1231 | Corresponds to NXmakenamedlink(handle,name,&ID) |
|---|
| 1232 | """ |
|---|
| 1233 | status = nxlib.nximakenamedlink_(self.handle,name,_ref(ID)) |
|---|
| 1234 | if status == ERROR: |
|---|
| 1235 | raise NeXusError, "Could not make link %s: %s"%(name,self._loc()) |
|---|
| 1236 | |
|---|
| 1237 | nxlib.nxisameid_.restype = c_int |
|---|
| 1238 | nxlib.nxisameid_.argtypes = [c_void_p, c_NXlink_p, c_NXlink_p] |
|---|
| 1239 | def sameID(self, ID1, ID2): |
|---|
| 1240 | """ |
|---|
| 1241 | Return True of ID1 and ID2 point to the same group/data. |
|---|
| 1242 | |
|---|
| 1243 | This should not raise any errors. |
|---|
| 1244 | |
|---|
| 1245 | Corresponds to NXsameID(handle,&ID1,&ID2) |
|---|
| 1246 | """ |
|---|
| 1247 | status = nxlib.nxisameid_(self.handle, _ref(ID1), _ref(ID2)) |
|---|
| 1248 | return status == OK |
|---|
| 1249 | |
|---|
| 1250 | nxlib.nxiopensourcegroup_.restype = c_int |
|---|
| 1251 | nxlib.nxiopensourcegroup_.argtyps = [c_void_p] |
|---|
| 1252 | def opensourcegroup(self): |
|---|
| 1253 | """ |
|---|
| 1254 | If the current node is a linked to another group or data, then |
|---|
| 1255 | open the group or data that it is linked to. |
|---|
| 1256 | |
|---|
| 1257 | Note: it is unclear how can we tell if we are linked, other than |
|---|
| 1258 | perhaps the existence of a 'target' attribute in the current item. |
|---|
| 1259 | |
|---|
| 1260 | Raises NeXusError. |
|---|
| 1261 | |
|---|
| 1262 | Corresponds to NXopensourcegroup(handle) |
|---|
| 1263 | """ |
|---|
| 1264 | status = nxlib.nxiopensourcegroup_(self.handle) |
|---|
| 1265 | if status == ERROR: |
|---|
| 1266 | raise NeXusError, "Could not open source group: %s"%(self._loc()) |
|---|
| 1267 | |
|---|
| 1268 | def link(self): |
|---|
| 1269 | """ |
|---|
| 1270 | Returns the item which the current item links to, or None if the |
|---|
| 1271 | current item is not linked. This is equivalent to scanning the |
|---|
| 1272 | attributes for target and returning it if target is not equal |
|---|
| 1273 | to self. |
|---|
| 1274 | |
|---|
| 1275 | This does not correspond to an existing NeXus API function, but |
|---|
| 1276 | combines the work of attrinfo/initattrdir/getnextattr/getattr. |
|---|
| 1277 | """ |
|---|
| 1278 | n = self.getattrinfo() |
|---|
| 1279 | self.initattrdir() |
|---|
| 1280 | for dummy in range(n): |
|---|
| 1281 | name,length,dtype = self.getnextattr() |
|---|
| 1282 | if name == "target": |
|---|
| 1283 | target = self.getattr(name,length,dtype) |
|---|
| 1284 | #print "target %s, path %s"%(target,self.path) |
|---|
| 1285 | if target != self.path: |
|---|
| 1286 | return target |
|---|
| 1287 | else: |
|---|
| 1288 | return None |
|---|
| 1289 | return None |
|---|
| 1290 | |
|---|
| 1291 | # ==== External linking ==== |
|---|
| 1292 | nxlib.nxiinquirefile_.restype = c_int |
|---|
| 1293 | nxlib.nxiinquirefile_.argtypes = [c_void_p, c_char_p, c_int] |
|---|
| 1294 | def inquirefile(self, maxnamelen=MAXPATHLEN): |
|---|
| 1295 | """ |
|---|
| 1296 | Return the filename for the current file. This may be different |
|---|
| 1297 | from the file that was opened (file.filename) if the current |
|---|
| 1298 | group is an external link to another file. |
|---|
| 1299 | |
|---|
| 1300 | Raises NeXusError if this fails. |
|---|
| 1301 | |
|---|
| 1302 | Corresponds to NXinquirefile(&handle,file,len) |
|---|
| 1303 | """ |
|---|
| 1304 | filename = ctypes.create_string_buffer(maxnamelen) |
|---|
| 1305 | status = nxlib.nxiinquirefile_(self.handle,filename,maxnamelen) |
|---|
| 1306 | if status == ERROR: |
|---|
| 1307 | raise NeXusError,\ |
|---|
| 1308 | "Could not determine filename: %s"%(self._loc()) |
|---|
| 1309 | return filename.value |
|---|
| 1310 | |
|---|
| 1311 | nxlib.nxilinkexternal_.restype = c_int |
|---|
| 1312 | nxlib.nxilinkexternal_.argtyps = [c_void_p, c_char_p, |
|---|
| 1313 | c_char_p, c_char_p] |
|---|
| 1314 | def linkexternal(self, name, nxclass, url): |
|---|
| 1315 | """ |
|---|
| 1316 | Return the filename for the external link if there is one, |
|---|
| 1317 | otherwise return None. |
|---|
| 1318 | |
|---|
| 1319 | Raises NeXusError if link fails. |
|---|
| 1320 | |
|---|
| 1321 | Corresponds to NXisexternalgroup(&handle,name,nxclass,file,len) |
|---|
| 1322 | """ |
|---|
| 1323 | status = nxlib.nxilinkexternal_(self.handle,name,nxclass,url) |
|---|
| 1324 | if status == ERROR: |
|---|
| 1325 | raise NeXusError,\ |
|---|
| 1326 | "Could not link %s to %s: %s"%(name,url,self._loc()) |
|---|
| 1327 | |
|---|
| 1328 | |
|---|
| 1329 | |
|---|
| 1330 | nxlib.nxiisexternalgroup_.restype = c_int |
|---|
| 1331 | nxlib.nxiisexternalgroup_.argtyps = [c_void_p, c_char_p, |
|---|
| 1332 | c_char_p, c_char_p, c_int] |
|---|
| 1333 | def isexternalgroup(self, name, nxclass, maxnamelen=MAXPATHLEN): |
|---|
| 1334 | """ |
|---|
| 1335 | Return the filename for the external link if there is one, |
|---|
| 1336 | otherwise return None. |
|---|
| 1337 | |
|---|
| 1338 | Corresponds to NXisexternalgroup(&handle,name,nxclass,file,len) |
|---|
| 1339 | """ |
|---|
| 1340 | url = ctypes.create_string_buffer(maxnamelen) |
|---|
| 1341 | status = nxlib.nxiisexternalgroup_(self.handle,name,nxclass, |
|---|
| 1342 | url,maxnamelen) |
|---|
| 1343 | if status == ERROR: |
|---|
| 1344 | return None |
|---|
| 1345 | else: |
|---|
| 1346 | return url.value |
|---|
| 1347 | |
|---|
| 1348 | # ==== Utility functions ==== |
|---|
| 1349 | def _loc(self): |
|---|
| 1350 | """ |
|---|
| 1351 | Return file location as string filename(path) |
|---|
| 1352 | |
|---|
| 1353 | This is an extension to the NeXus API. |
|---|
| 1354 | """ |
|---|
| 1355 | return "%s(%s)"%(self.filename,self.path) |
|---|
| 1356 | |
|---|
| 1357 | def _poutput(self, dtype, shape): |
|---|
| 1358 | """ |
|---|
| 1359 | Build space to collect a nexus data element. |
|---|
| 1360 | Returns data,pdata,size,datafn where |
|---|
| 1361 | - data is a python type to hold the returned data |
|---|
| 1362 | - pdata is the pointer to the start of the data |
|---|
| 1363 | - size is the number of bytes in the data block |
|---|
| 1364 | - datafn is a lamba expression to extract the return value from data |
|---|
| 1365 | Note that datafn can return a string, a scalar or an array depending |
|---|
| 1366 | on the data type and shape of the data group. |
|---|
| 1367 | """ |
|---|
| 1368 | if isinstance(shape, int): |
|---|
| 1369 | shape = [shape] |
|---|
| 1370 | if len(shape) == 1 and dtype == 'char': |
|---|
| 1371 | # string - use ctypes allocator |
|---|
| 1372 | size = int(shape[0]) |
|---|
| 1373 | data = ctypes.create_string_buffer(size) |
|---|
| 1374 | pdata = data |
|---|
| 1375 | datafn = lambda: data.value |
|---|
| 1376 | else: |
|---|
| 1377 | # scalar, array or string list - use numpy array |
|---|
| 1378 | if dtype=='char': |
|---|
| 1379 | data = numpy.zeros(shape[:-1], dtype='S%i'%shape[-1]) |
|---|
| 1380 | else: |
|---|
| 1381 | data = numpy.zeros(shape, dtype) |
|---|
| 1382 | if len(shape) == 1 and shape[0] == 1: |
|---|
| 1383 | datafn = lambda: data[0] |
|---|
| 1384 | else: |
|---|
| 1385 | datafn = lambda: data |
|---|
| 1386 | pdata = data.ctypes.data |
|---|
| 1387 | size = data.nbytes |
|---|
| 1388 | return data,pdata,size,datafn |
|---|
| 1389 | |
|---|
| 1390 | def _pinput(self, data, dtype, shape): |
|---|
| 1391 | """ |
|---|
| 1392 | Convert an input array to a C pointer to a dense array. |
|---|
| 1393 | |
|---|
| 1394 | Returns data, pdata where |
|---|
| 1395 | - data is a possibly new copy of the array |
|---|
| 1396 | - pdata is a pointer to the beginning of the array. |
|---|
| 1397 | Note that you must hold a reference to data for as long |
|---|
| 1398 | as you need pdata to keep the memory from being released to the heap. |
|---|
| 1399 | """ |
|---|
| 1400 | if isinstance(shape, int): |
|---|
| 1401 | shape = [shape] |
|---|
| 1402 | if dtype == "char": |
|---|
| 1403 | data = numpy.asarray(data, dtype='S%d'%(shape[-1])) |
|---|
| 1404 | else: |
|---|
| 1405 | # Convert scalars to vectors of length one |
|---|
| 1406 | if numpy.prod(shape) == 1 and not hasattr(data,'shape'): |
|---|
| 1407 | data = numpy.array([data], dtype=dtype) |
|---|
| 1408 | # Check that dimensions match |
|---|
| 1409 | # Ick! need to exclude dimensions of length 1 in order to catch |
|---|
| 1410 | # array slices such as a[:,1], which only report one dimension |
|---|
| 1411 | input_shape = numpy.array([i for i in data.shape if i != 1]) |
|---|
| 1412 | target_shape = numpy.array([i for i in shape if i != 1]) |
|---|
| 1413 | if len(input_shape) != len(target_shape) \ |
|---|
| 1414 | or (input_shape != target_shape).any(): |
|---|
| 1415 | raise ValueError,\ |
|---|
| 1416 | "Shape mismatch %s!=%s: %s"%(data.shape,shape,self._loc()) |
|---|
| 1417 | # Check data type |
|---|
| 1418 | if str(data.dtype) != dtype: |
|---|
| 1419 | raise ValueError,\ |
|---|
| 1420 | "Type mismatch %s!=%s: %s"%(dtype,data.dtype,self._loc()) |
|---|
| 1421 | |
|---|
| 1422 | data = numpy.ascontiguousarray(data) |
|---|
| 1423 | pdata = data.ctypes.data |
|---|
| 1424 | |
|---|
| 1425 | return data,pdata |
|---|
| 1426 | |
|---|
| 1427 | def show(self, path=None, indent=0): |
|---|
| 1428 | """ |
|---|
| 1429 | Print the structure of a NeXus file from the current node. |
|---|
| 1430 | |
|---|
| 1431 | TODO: Break this into a tree walker and a visitor. |
|---|
| 1432 | """ |
|---|
| 1433 | oldpath = self.path |
|---|
| 1434 | self.openpath(path) |
|---|
| 1435 | |
|---|
| 1436 | print "=== File",self.inquirefile(),path |
|---|
| 1437 | self._show(indent=indent) |
|---|
| 1438 | self.openpath(oldpath) |
|---|
| 1439 | |
|---|
| 1440 | def _show(self, indent=0): |
|---|
| 1441 | """ |
|---|
| 1442 | Print the structure of a NeXus file from the current node. |
|---|
| 1443 | |
|---|
| 1444 | TODO: Break this into a tree walker and a visitor. |
|---|
| 1445 | """ |
|---|
| 1446 | prefix = ' '*indent |
|---|
| 1447 | link = self.link() |
|---|
| 1448 | if link: |
|---|
| 1449 | print "%(prefix)s-> %(link)s" % locals() |
|---|
| 1450 | return |
|---|
| 1451 | for attr,value in self.attrs(): |
|---|
| 1452 | print "%(prefix)s@%(attr)s: %(value)s" % locals() |
|---|
| 1453 | for name,nxclass in self.entries(): |
|---|
| 1454 | if nxclass == "SDS": |
|---|
| 1455 | shape,dtype = self.getinfo() |
|---|
| 1456 | dims = "x".join([str(x) for x in shape]) |
|---|
| 1457 | print "%(prefix)s%(name)s %(dtype)s %(dims)s" % locals() |
|---|
| 1458 | link = self.link() |
|---|
| 1459 | if link: |
|---|
| 1460 | print " %(prefix)s-> %(link)s" % locals() |
|---|
| 1461 | else: |
|---|
| 1462 | for attr,value in self.attrs(): |
|---|
| 1463 | print " %(prefix)s@%(attr)s: %(value)s" % locals() |
|---|
| 1464 | if numpy.prod(shape) < 8: |
|---|
| 1465 | value = self.getdata() |
|---|
| 1466 | print " %s%s"%(prefix,str(value)) |
|---|
| 1467 | else: |
|---|
| 1468 | print "%(prefix)s%(name)s %(nxclass)s" % locals() |
|---|
| 1469 | self._show(indent=indent+2) |
|---|
| 1470 | |
|---|
| 1471 | |
|---|
| 1472 | __id__ = "$ID$" |
|---|