PyAims high-level functions and classes

This section is the “useful” part, what you need to know first.

IO system

soma.aims.read(filename, border=0, frame=- 1, dtype=None, allocmode=None, options=None, object=None)[source]

Equivalent to:

r = Reader(allocmode=allocmode, options=options)
return r.read(filename, border=border, frame=frame, dtype=dtype,
              object=object)

Ex:

  • read a volume:

    vol = aims.read('file.nii')
    
  • read a mesh:

    mesh = aims.read('file.gii')
    
  • read part of a volume, with borders (warning, all formats do not allow it):

    vol = aims.read(
        'file.nii', options={'border': 2, 'ox': 100, 'sx': 100,
                             'oy': 50, 'sy': 150, 'oz': 20, 'sz': 60})
    

    or (equivalent):

    vol = aims.read(
      'file.nii?border=2&ox=100&sx=100&oy=50&sy=150&oz=20&sz=60')
    
  • read a view inside a larger volume (warning, all formats do not allow it, and they may produce inpredictible results, and probably crashes):

    border = aims.Volume_S16(500, 500, 200)
    view = aims.VolumeView(border, [50, 50, 50], [256, 256, 124])
    aims.read('file.nii', object=view, options={'keep_allocation': True})
    
soma.aims.write(obj, filename, format=None, options={})[source]

Equivalent to:

w = Writer() w.write(obj, filename, format=format, options=options)

Neuroimaging data structures

soma.aims.Volume(*args, **kwargs)[source]

Create an instance of Aims Volume (Volume_<type>) from a type parameter, which may be specified as the dtype keyword argument, or as one of the arguments if one is identitied as a type. The default type is ‘FLOAT’. Type definitions should match those accepted by typeCode(). Volume may also use a numpy array, or another Volume as unique argument.

Note that Volume( Volume_* ) actually performs a copy of the data, whereas Volume( rc_ptr_Volume_* ) share the input data.

Volume classes bindings are instantiated on a number of types: U8, S16, U16, S32, U32, FLOAT, DOUBLE, RGB, RGBA. For an example of the volumes API, see the soma.aims.Volume_FLOAT class.

soma.aims.AimsTimeSurface(*args, **kwargs)[source]

Create an instance of Aims mesh (AimsTimeSurface_<dim>_<dtype>) from a dimension parameter, or copies a mesh, or build it from arrays.

See for instance AimsTimeSurface_3_VOID

Usages:

mesh1 = AimsTimeSurface()
mesh2 = AimsTimeSurface(3)
mesh3 = AimsTimeSurface(3, 'VOID')
mesh4 = AimsTimeSurface(3, dtype='VOID')
mesh5 = AimsTimeSurface(dim=3, dtype='VOID')
mesh6 = AimsTimeSurface(mesh)
mesh7 = AimsTimeSurface(vertices=[[1,2,3], [3,4,5.6]],
                        polygons=[[0,1]])
mesh8 = AimsTimeSurface([[1,2,3], [3,4,5.6]], polygons=[[0,1]])
mesh9 = AimsTimeSurface([[1,2,3], [3,4,5.6]], None, [[0,1]])
mesh10 = AimsTimeSurface([[1,2,3], [3,4,5.6]], [[0,1]], normals=[])

For an example of mesh surface classes, see the soma.aims.AimsTimeSurface_3_VOID class.

soma.aims.TimeTexture(*args, **kwargs)[source]

Create an instance of Aims texture (TimeTexture_<type>) from a type parameter, which may be specified as the dtype keyword argument, or as one of the arguments if one is identitied as a type.

The default type is ‘FLOAT’.

Type definitions should match those accepted by typeCode(). TimeTexture may also use a numpy array, or another TimeTexture_* or Textrue_* as unique argument.

Building from a numpy arrays uses the 1st dimension as vertex, the 2nd as time (if any).

TimeTexture classes bindings are instantiated on a number of types: S16, S32, U32, FLOAT, POINT2DF. For an example of the TimeTexture API, see the soma.aims.TimeTexture_FLOAT class.

soma.aims.Texture(*args, **kwargs)[source]

Create an instance of Aims low-level texture (Texture_<type>) from a type parameter, which may be specified as the dtype keyword argument, or as one of the arguments if one is identitied as a type.

The default type is ‘FLOAT’.

Type definitions should match those accepted by typeCode().

Texture may also use a numpy array, or another Textrue_* as unique argument.

Texture classes bindings are instantiated on a number of types: S16, S32, U32, FLOAT, POINT2DF. For an example of the Texture API, see the soma.aims.Texture_FLOAT class.

Note

You normally seldom use the Texture classes directly: most of the time they are (and should be) used through the higher-level soma.aims.TimeTexture() function and classes.

soma.aims.BucketMap(*args, **kwargs)[source]

Create an instance of Aims bucket (BucketMap_<type>) from a type parameter, which may be specified as the dtype keyword argument, or as one of the arguments if one is identitied as a type.

The default type is ‘VOID’.

Type definitions should match those accepted by typeCode().

For an example of bucket classes, see the soma.aims.BucketMap_VOID class, which is actually the only instantiation bound to Python, because textured buckets are not used directly.

Transformations and coordinates systems

class soma.aims.AffineTransformation3d(*args)

See the AffineTransformation3d C++ documentation

isIdentity()

Test if the transformation can safely be omitted

This method must only return true if the transformation behaves exactly like an identity transformation (notably, the transform methods will always return the input coordinates unchanged).

NOTE: Implementors of derived classes may choose to always return false if a test would be difficult to implement or expensive to run.

toMatrix()

This function return a copy of the transformation matrix

class soma.aims.Quaternion
buildFromMatrix(m)
Parameters

m (vector_FLOAT) – 4x4 matrix in columns (OpenGL-style). Be careful that it is not the expected order for a numpy array using ravel(), which have to be transposed.

compose(value, /)

Return self*value.

class soma.aims.StandardReferentials
acPcReferential()

identifer (str) for the Taialairach/AIMS space

acPcReferentialID()

identifer (UUID str) for the Talairach/AIMS space

icbm2009cTemplateHeader()

hdr = aims.StandardReferentials.icbm2009cTemplateHeader() Return a header with size and characteristics of the ICBM2009c MNI template volume.

mniTemplateReferential()

identifer (str) for the MNI/ICBM space

mniTemplateReferentialID()

identifer (UUID str) for the MNI/ICBM space

talairachReferential()

identifer (str) for the Talairach/CIFTI space

talairachToICBM2009cTemplate()

trans = aims.StandardReferentials.talairachToICBM2009cTemplate()

Transformation between the ICBM152 standard space and the ICBM152 template space.

includes shifts and axes inversions.

This is the “most standard” field of view of the template image, the one from the ICBM2009c_nlin_asym template from the MNI. Actually we find various fields of view for the templates. The one we use here is (193, 229, 193). The transform to the “real” ICBM space (0 roughly at AC) is provided in the output transformation header. This space has a fixed transformation with our Aims Talairach, along with the template volume size and voxel size. This space has a fixed transformation with our Aims Talairach.

General purpose C++ containers

class soma.aims.Object

Generic dynamic polymorphic object proxy.

Object is a proxy and a reference counter to a GenericObject. In most cases, Object and GenericObject are interchangeable and play the same role, there are two objects for technical reasons in the C++ layer, but in Python it whould make no difference.

The C++ generic object is an implementation of a mutable dynamic container which can hold any type of concrete data, with a generic access API which abstracts the concrete type of the object stored within it.

According to the underlying object type, Object can behave like a number, a string, or a container: sequence or dictionary. The python bindings can therefore support the sequence protocol, or the dictionary protocol, like a ‘real’ python object.

The only part which makes it visibly different from a python object is the conversions with stored objects, which are generally C++ objects (but can also be any python objects). Transparent conversions are done when possible.

We will take an example to show how to use it: a volume header. Suppose the files volume.img/volume.hdr[/volume.img.minf] represent a 3D volume (a MRI volume, typically, here in Analyze/SPM or Nifti format). We will read it using aims, and access its header data, which comes as a generic object.

>>> from __future__ import print_function # work using python 2 or 3
>>> from soma import aims
>>> vol = aims.read('volume.img')
>>> hdr = vol.header()

Now vol is the volume, and hdr its header, of type Object.

>>> type(hdr)
<class 'soma.aims.Object'>
>>> print(hdr)
{ 'voxel_size' : [ 1.875, 1.875, 4, 1 ], 'file_type' : 'SPM',
'byte_swapping' : 0, 'volume_dimension' : [ 128, 128, 16, 1 ],
'vox_units' : 'mm  ', 'cal_units' : '', 'data_type' : 'S16',
'disk_data_type' : 'S16', 'bits_allocated' : 2, 'tr' : 1,
'minimum' : 0, 'maximum' : 13645, 'scale_factor' : 1,
'scale_factor_applied' : 0, 'db_name' : '',
'aux_file' : '', 'orient' : 3, 'generated' : '', 'scannum' : '',
'patient_id' : '', 'exp_date' : '', 'exp_time' : '', 'views' : 0,
'start_field' : 32768, 'field_skip' : 8192, 'omax' : 0,
'omin' : 0, 'smax' : 32, 'smin' : 0, 'SPM_data_type' : '',
'possible_data_types' : [ 'S16' ], 'spm_radio_convention' : 1,
'spm_origin' : [ 65, 65, 9 ], 'origin' : [ 64, 63, 7 ] }

hdr prints like a dictionary, but is not really a python dictionary object. However it behaves like a dictionary:

>>> print(hdr['voxel_size'])
[ 1.875, 1.875, 4, 1 ]
>>> print(hdr['data_type'])
S16
>>> print(hdr['voxel_size'][0])
1.875
>>> for x in hdr:
>>>     print(x)
>>>
voxel_size
file_type
byte_swapping
volume_dimension
vox_units
cal_units
data_type
disk_data_type
bits_allocated
tr
minimum
maximum
scale_factor
scale_factor_applied
db_name
aux_file
orient
generated
scannum
patient_id
exp_date
exp_time
views
start_field
field_skip
omax
omin
smax
smin
SPM_data_type
possible_data_types
spm_radio_convention
spm_origin
origin
>>> for x,y in hdr.items():
>>>     print(x, ':', y)
>>>
voxel_size : [ 1.875, 1.875, 4, 1 ]
file_type : 'SPM'
byte_swapping : 0
volume_dimension : [ 128, 128, 16, 1 ]
vox_units : 'mm  '
cal_units : ''
data_type : 'S16'
disk_data_type : 'S16'
bits_allocated : 2
tr : 1
minimum : 0
maximum : 13645
scale_factor : 1
scale_factor_applied : 0
db_name : ''
aux_file : ''
orient : 3
generated : ''
scannum : ''
patient_id : ''
exp_date : ''
exp_time : ''
views : 0
start_field : 32768
field_skip : 8192
omax : 0
omin : 0
smax : 32
smin : 0
SPM_data_type : ''
possible_data_types : [ 'S16' ]
spm_radio_convention : 1
spm_origin : [ 65, 65, 9 ]
origin : [ 64, 63, 7 ]

Elements returned by the dictionary queries can have various types, althrough they are all stored internally in a C++ structure through soma.aims.Object wrappers. But when the underlying type of the element is known and can be retreived as a python object (either a standard python type or a python binding of a C++ class), it is done automatically. Such conversion is not possible when the underlying object has no python binding and is a pure C++ object, or when there is no conversion function. In this case, an Object is returned and contains the selected data.

Conversions are done using conversion methods that you generally do not need to call by hand: getScalar(), getString(), or the more general conversion method soma.aims.Object.getPython(). Elements types that cannot be converted to python concrete types are retreived in their Object container.

The generic conversion function in Object: getPython, is a static method and can be extended.

Elements in Object containers can generally be read and written:

>>> hdr['data_type'] = 'FLOAT'
>>> hdr['data_type']
'FLOAT'

This generally suits your needs. But in a few cases there will be a problem in internal types handling in the C++ layer.

Here, the underlying C++ generic object which did hold a C++ string (std::string) 'S16' is now replaced by another generic object built from the python string 'FLOAT', and which now wraps an element of type PyObject (or PyString). It just behaves the same way, so this operation is perfectly valid, but if C++ programs expect it to be a C++ std::string, they may fail.

Moreover, when writing back to existing concrete objects, some additional conversions may take place: for instance hdr['voxel_size'] is a C++ object of type std::vector<float>, so writing to hdr['voxel_size'][0] needs to enure we are actually writing a float, and if not, convert to it if possible.

You can query a type identifier for a generic object via the type() method:

>>> hdr.type()
'PropertySet'

Objects that can be converted from C++ generic objects to python are not necessarily known in advance. A conversion table is kept in the global variable soma.aims.convertersObjectToPython (in the aims module) and can be extended. Therefore other python modules using aims (such as sigraph) can extend this conversion table.

Details and tricks

There are some limitations and “unexpected behaviours” caused by the underlying C++ implementation of generic objects, and general behaviour differences between Python and C++. The following is expert-level details, so read it only if you have problems or you are a C++ expert with good knowledge of the cartobase C++ library…

Generic object - specialized object conversions

Putting a specific object in a generic Object makes a copy of it the first time it is done, because C++ generic objects contain the specialized elements. Once this has been done once, generic objects are shared and not copied anymore, which is consistent with the normal python behaviour. The non-pythonic thing is the first insertion in a generic object:

>>> a = aims.vector_FLOAT([12.6, -5.7])
>>> print(a)
[ 12.6, -5.7 ]
>>> hdr['foo'] = a # here a is copied into hdr['foo']
>>> a.append( 6.8 ) # a is changed but not hdr
>>> print(a)
[ 12.6, -5.7, 6.8 ]
>>> print(hdr['foo'])
[ 12.6, -5.7 ]
>>> hdr['dummy'] = hdr['foo']
>>> hdr['foo'].append(4.2)
>>> print(hdr['dummy']) # this time hdr['dummy'] is changed
[ 12.6, -5.7, 4.2 ]

To overcome the first copy problem, you may have to reassign the initial variable to the copy instance:

>>> a = aims.vector_FLOAT([1.2, 2.3, 3.4])
>>> hdr['foo'] = a
>>> a = hdr['foo']
>>> print(hdr['foo'])
[ 1.2, 2.3, 3.4 ]
>>> a[1] = 12.8
>>> print(a)
[ 1.2, 12.8, 3.4 ]
>>> print(hdr['foo'])
[ 1.2, 12.8, 3.4 ]

There are exceptions to this behaviour:

  • pure python objects, like lists or dictionaries are never copied since it is only a pointer to them (the C PyObject * pointer) which is stored.

  • small builtin types: numbers and strings are always copied since they are always converted and copied, not wrapped, when passed from python to C++ and vice versa.

The get() method

On Object the get method is ambiguous and has 2 meanings:

  • get() without arguments is a wrapping to the C++ rc_ptr::get() which returns the underlying wrapped object (here, a GenericObject)

  • get(key, default=None) is the dict-like method that is available on GenericObject since aims 4.6.1.

Depending on the arguments the Object method will redirect to the right one, thus:

myobject.get()            # gets the GenericObject
myobject.get('key')       # gets the dict item under keyt 'key'
myobject.get().get('key') # does both, but this is what the previous line
                          # already does, so the result is the same

To avoid this ambiguity, Object.get() and rc_ptr.get() have been renamed _get() years ago (in aims 4.0 I think) but get() is still present for compatibility. This use (without argument) is obsolete and may be removed in the future.

See the Object C++ documentation

getPython()

Conversion to python types: extracts what is in the Object (when possible). The global dictionary soma.aims.convertersObjectToPython stores converters

soma.aims.AimsVector(*args, **kwargs)[source]

Create an AimsVector instance from type and dimension arguments. Types may be passed as the keyword argument dtype. Otherwise the arguments are parsed to find types arguments. Dimension should be passed as the keyword argument dim, or is guessed from the input value(s).

Types may be specified as allowed by typeCode().

If unspecified, type is guessed from the 1st element of the vector data.

soma.aims.stdVector(*args, **kwargs)[source]

Create an instance of STL C++ vector (vector_<type>) from a type parameter, which may be specified as the dtype keyword argument, or as one of the arguments if one is identitied as a type.

Type definitions should match those accepted by typeCode().

soma.aims.stdSet(*args, **kwargs)[source]

Create an instance of STL C++ set (set_<type>) from a type parameter, which may be specified as the dtype keyword argument, or as one of the arguments if one is identitied as a type.

Type definitions should match those accepted by typeCode().

soma.aims.stdList(*args, **kwargs)[source]

Create an instance of STL C++ list (list_<type>) from a type parameter, which may be specified as the dtype keyword argument, or as one of the arguments if one is identitied as a type.

Type definitions should match those accepted by typeCode().

soma.aims.rc_ptr(*args, **kwargs)[source]

Create an instance of aims reference-counting object (rc_ptr_<type>) from a type parameter, which may be specified as the dtype keyword argument, or as one of the arguments if one is identitied as a type.

Type definitions should match those accepted by typeCode().

Conversion classes and functions

soma.aims.Converter(*args, **kwargs)[source]

Create a Converter instance from input and output types. Types may be passed as keyword arguments intype and outtype, or dtype if both are the same (not very useful for a converter). Otherwise the arguments are parsed to find types arguments.

Types may be specified as allowed by typeCode().

Note that for volumes, the method astype is often more convenient than using a Converter object (with the same result).

soma.aims.ShallowConverter(*args, **kwargs)[source]

Create a ShallowConverter instance from input and output types. Types may be passed as keyword arguments intype and outtype, or dtype if both are the same (not very useful for a converter). Otherwise the arguments are parsed to find types arguments.

Types may be specified as allowed by typeCode().

Note that for volumes, the method astype is often more convenient than using a Converter object (with the same result).

Graphs and ROIs

class soma.aims.GraphManip

Graph manipulation, which make things easier than the direct use of Graph. See the GraphManip C++ documentation

getICBM2009cTemplateTransform()

transform = aims.GraphManip.getICBM2009cTemplateTransform(graph)

Extract the transformation to the MNI ICBM152 space, shifted to the “most standard” field of view of the template image, the one from the ICBM2009c_nlin_asym template from the MNI. Actually we find various fields of view for the templates. The one we use here is (193, 229, 193). The transform to the “real” ICBM space (0 roughly at AC) is provided in the output transformation header. This space has a fixed transformation with our Aims Talairach, along with the template volume size and voxel size. This space has a fixed transformation with our Aims Talairach. Includes shifts and axes inversions.

getICBMTransform()

transform = aims.GraphManip.getICBMTransform(graph)

Extract the transformation to the MNI ICBM152 space. This space has a fixed transformation with our Aims Talairach.

get_element_table()

Get the GraphElementTable as a dict. The dict has the following shape:

{
    syntax_name: {
        id_name: GraphElementCode_object
    }
}

GraphElementCode objects are given as dicts (str, str):

{
    'id': <same_as_key: filename (globals) or attribute for filename (locals)>,
    'attribute': <attribute_name_in_graph_element>,
    'objectType': <type of object>,
    'dataType': <data type of object>,
    'storageType': <'Global', 'Local', or 'GlobalPacked'>,
    'local_file_attribute': <attribute giving the filename for local storage in each graph elememt>,
    'global_index_attribute': <attribute giving the index for global storage in each graph elememt>,
    'global_filename': <filename for global storage>,
    'global_attribute': <attribute>,
    'syntax': <syntax_name>
}
graphFromVolume()

graph = graphFromVolume(vol, background=0, trans=None) graphFromVolume(vol, graph, background=0, trans=None,

automaticBackgroundSearch=True)

builds a ROI graph from a volume of labels

class soma.aims.RoiIterator(*args)
class soma.aims.MaskIterator(*args)

Precisions about aims / numpy bindings

Several data structures are “array containers”, arrays of items packed in memory: vectors and texture wrappers, AimsVectors, volumes etc. They may be 1 dimensional (vectors) or multi-dimensional (volumes). Many of such data structures offer bindings to the numpy library.

Such bindings are normally accessed by using the numpy numpy.asarray() or numpy.array() functions on the aims object.

The array returned is a reference to the actual data block, so any modification to its contents also affect the Volume contents, so it is generally an easy way of manipulating array items because all the power of the numpy module can be used there.

Arrays of non-scalar items

Since PyAims 4.7 the numpy arrays bindings have notably improved, and are now able to bind arrays to voxels types which are not scalar numeric types. Volumes or vectors of RGB, RGBA, HSV, or Point3df now have numpy bindings. The bound object have generally a “numpy struct” binding, that is not the usual C++/python object binding, but instead a structure managed by numpy which also supports indexing. For most objects we are using, they also have an array stucture (a RGB is an array with 3 int8 items), and are bound under a sturcture with a unique field, named “v” (for “vector”).

However we chose not to implement these array items the same way in Volume or in vector containers. In Volumes, we are actually using the structure elements:

>>> v = aims.Volume('RGB', 100, 100, 10)
>>> numpy.asarray(v)[0, 0, 0, 0]
(0, 0, 0)

Such an array may be indexed by the field name, which returns another array with scalar values and additional dimensions:

>>> numpy.asarray(v)['v']
>>> numpy.asarray(v)['v'].dtype
uint8
>>> numpy.asarray(v)['v'].shape
(100, 100, 100, 10, 3)

Both arrays share their memory with the aims volume.

In vectors however (vector or AimsVector) which are 1D array containers, we found more convenient to directly wrap the ‘v’ field when possible, so as to obtain a regular 2D array, which is what is expected most of the time:

>>> v = aims.vector_POINT3DF([(1.5, 2., 3), (4,5,.6), (7, 8., 9.1)])
>>> numpy.asarray(v)
array([[1.5, 2. , 3. ],
      [4. , 5. , 0.6],
      [7. , 8. , 9.1]], dtype=float32)

Note that the same code in PyAims 4.6 or earlier will look exactly the same, except that:

  • in pyaims 4.6 the array conversion is very slow, because numpy iterates over each element of each item of the array (probably using a python loop), since it does not know about its packed array structure in memory. In 4.7 it is essentially immediate.

  • in pyaims 4.6 the array obtained this way is a copy, and thus its modification does not affect the original vector. In pyaims 4.7 the memory is shared.

If one actually wants to get the array of structure objects as for Volumes, then the array_struct() method is here:

>>> v.array_struct()
array([([1.5, 2. , 3. ],), ([4. , 5. , 0.6],), ([7. , 8. , 9.1],)],
      dtype=[('v', '<f4', (3,))])