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
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 pni.io.nx.h5 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)
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
NeXus_version : /@NeXus_version 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
There are basically three ways to to access the attributes attached to a group or a field
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["NeXus_version"].name)
The output is
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).