source: trunk/bindings/python/nxs/napi.py @ 1781

Revision 1781, 51.6 KB checked in by Eugenwintersberger, 2 months ago (diff)

Started with unit tests for the python binding.
refs #296

  • Property svn:executable set to *
Line 
1# This program is public domain
2# Author: Paul Kienzle
3
4"""@package nxs.napi
5Wrapper for the NeXus shared library.
6
7Use this interface when converting code from other languages which
8do not support the natural view of the hierarchy.
9
10Library Location
11================
12
13This wrapper needs the location of the libNeXus precompiled binary. It
14looks 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
33The import will raise an OSError exception if the library wasn't found
34or couldn't be loaded.  Note that on Windows in particular this may be
35because the supporting HDF5 dlls were not available in the usual places.
36
37If you are extracting the nexus library from a bundle at runtime, set
38os.environ['NEXUSLIB'] to the path where it is extracted before the
39first import of nxs.
40
41Example
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
54Interface
55=========
56
57When converting code to python from other languages you do not
58necessarily want to redo the file handling code.  The nxs
59provides an interface which more closely follows the
60NeXus application programming interface (NAPI_).
61
62This 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
73File 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
84Dimension constants::
85
86  - nxs.UNLIMITED  - for the extensible data dimension
87  - nxs.MAXRANK    - for the number of possible dimensions
88
89Data 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
97You can use the numpy A.dtype attribute for the type of array A.
98
99Dimensions are lists of integers or numpy arrays.  You can use the
100numpy A.shape attribute for the dimensions of array A.
101
102Compression codes are::
103
104 'none' 'lzw' 'rle' 'huffman'
105
106  As of this writing NeXus only supports 'none' and 'lzw'.
107
108Miscellaneous 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
114Caveats
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.
121This 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
134import sys, os, numpy, ctypes
135
136# Defined ctypes
137from ctypes import c_void_p, c_int, c_int64, c_long, c_char, c_char_p
138from ctypes import byref as _ref
139c_void_pp = ctypes.POINTER(c_void_p)
140c_int_p = ctypes.POINTER(c_int)
141c_int64_p = ctypes.POINTER(c_int64)
142class _NXlink(ctypes.Structure):
143    _fields_ = [("iTag", c_long),
144                ("iRef", c_long),
145                ("targetPath", c_char*1024),
146                ("linktype", c_int)]
147    _pack_ = False
148c_NXlink_p = ctypes.POINTER(_NXlink)
149
150
151# Open modes:
152ACC_READ,ACC_RDWR,ACC_CREATE=1,2,3
153ACC_CREATE4,ACC_CREATE5,ACC_CREATEXML=4,5,6
154_nxopen_mode=dict(r=1,rw=2,w=3,w4=4,w5=5,wx=6)
155NOSTRIP=128
156
157# Status codes
158OK,ERROR,EOD=1,0,-1
159
160# Other constants
161UNLIMITED=-1
162MAXRANK=32
163MAXNAMELEN=64
164MAXPATHLEN=1024 # inferred from code
165
166# bogus groups; these groups are ignored in HDFView from NCSA.
167H4SKIP = ['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
192def _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
203def _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
211def _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
262def _init():
263    lib = _libnexus()
264    lib.NXMDisableErrorReporting()
265    return lib
266
267# Define the interface to the dll
268nxlib = _init()
269
270
271def open(filename, mode='r'):
272    """
273    Returns a NeXus file object.
274    """
275    return NeXus(filename, mode)
276
277class NeXusError(Exception):
278    """NeXus Error"""
279    pass
280
281class 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$"
Note: See TracBrowser for help on using the repository browser.