Working with attributes

Fields and groups can have attributes attached which provide additional metadata. In fact Nexus makes heavy use of attributes to store additional information about a field or group. It is thus not recommended to use attributes too excessively as one may runs into name conflicts with future Nexus development. Attributes can be accessed from fields and groups via their attributes member. Attributes behave basically like fields with some restrictions

  • they cannot grow

  • one cannot apply compression to attributes.

Due to this restrictions one should not use attributes to store large amounts of data.

To create attributes use the create() method of the attributes member

import pninexus.nexus as nx

field = ....

a = field.attributes.create("temperature",type="float32")

which would create a scalar attribute of name temperature and with a 32-bit floating point type. Multidimensional attributes can be created with the optional shape keyword argument

a = field.attributes.create("temperature",type="float32",shape=(4,4))

If an attribute already exists it can be overwritten using the overwrite keyword argument (False by default).

a = field.attributes.create("temperature",type="float32",shape=(4,4))
a = field.attributes.create("temperature",type="int32",shape=(3,5),
                            overwrite=True)

Attribute inquiry

Like fields and groups attributes have a couple of properties which can be queried to obtain metadata for a particular attribute instance

property

description

shape

a tuple with the number of elements along each dimension of the attribute

dtype

the string representation of the attributes data type

is_valid

true if the attribute is a valid object

name

the name of the attribute (the key which can be used to retrieve the attribute from its parent)

value

provides access to the attributes data

path

returns the path of the attribute

The following code

#!/usr/bin/env python
#File: attributes_properties.py

from __future__ import print_function
import pni.io.nx.h5 as nexus

f = nexus.create_file("attributes_properties.nxs",overwrite=True)
r = f.root()

fmt = "{name:20} : {path:20} type={dtype:<10} size={size:<20}"

for a in r.attributes:
    print(fmt.format(name=a.name,
                     path=a.path,
                     dtype=a.dtype,
                     size=a.size))

f.close()

will produce

HDF5_Version         : /@HDF5_Version       type=string     size=1
NX_class             : /@NX_class           type=string     size=1
file_name            : /@file_name          type=string     size=1
file_time            : /@file_time          type=string     size=1
file_update_time     : /@file_update_time   type=string     size=1

Attribute retrieval and iteration

There are basically three ways to to access the attributes attached to a group or a field

  • by name via __getitem__() (using [])

  • by the attributes index

  • by an iterator.

The attributes attribute of groups and fields exposes an iterable interface. The following example shows all three ways how to access the attributes of a files root group

#!/usr/bin/env python
#File: attribute_access.py
from __future__ import print_function
import pni.io.nx.h5 as nexus 

f = nexus.create_file("attributes_access.nxs",overwrite=True)
r = f.root()

#access attributes via their index
print("Access attributes via index")
for index in range(len(r.attributes)):
    print("{index}: {name}".format(index=index,
                                   name=r.attributes[index].name))

#access attributes via iterator
print()
print("Access attributes via iterator")
for attr in r.attributes:
    print(attr.name)

#access directly via name 
print(r.attributes["HDF5_Version"].name)

The output is

Reading and writing data from and to an attribute

Concerning IO operations attributes heave pretty much like fields as shown in the next example

#!/usr/bin/env python
#File: attribute_io.py
from __future__ import print_function
import numpy
import pni.io.nx.h5 as nexus 

f = nexus.create_file("attribute_io.nxs",overwrite=True)
samptr = f.root().create_group("scan_1","NXentry"). \
                  create_group("instrument","NXinstrument"). \
                  create_group("sample","NXsample"). \
                  create_group("transformations","NXtransformations")

samptr.parent.create_field("depends_on","string")[...]="transformation/tt"

tt = samptr.create_field("tt","float64",shape=(10,))
tt[...] = numpy.array([1,2,3,4,5,6,7,8,9,10])
tt.attributes.create("transformation_type","str")[...] = "rotation"
a = tt.attributes.create("vector","float64",shape=(3,))
a[...] = numpy.array([-1,0,0])

print("r=",a[...])
print("x=",a[0])
print("y=",a[1])
print("z=",a[2])

r= [-1.  0.  0.]
x= -1.0
y= 0.0
z= 0.0

There is not too much more to day about that. When reading and writing multidimensional data numpy arrays must be used in any case (also for strings).