7.5. Advanced Issues on Registered Voxel Types

The following paragraphs describe some features for advanced configurations of your ML module. This includes:

7.5.1. About the Difference Between Scalar, Extended and Registered Voxel Types

There are three different kinds of voxels types you need to distinguish when you want to understand how the ML works in detail.

  • Scalar Voxel Types

    Scalar voxel types are primitive data types. They are available in many programming languages, such as signed and unsigned 8, 16, 32 and/or 64 bit sized integers, float and double types. They are also the most typical types used for image voxels.

    In the ML, these types are called MLuint8, MLint8, MLuint16, MLint16, MLuint32, MLint32, MLint64, MLfloat, and MLdouble. There are also enumerator constants called MLuint8Type, MLint8Type, MLuint16Type, MLint16Type, MLuint32Type, MLint32Type, MLfloatType, and MLdoubleType, respectively.

    [Note]Note

    For compatibility reasons, the MLuint64 type is not supported in the ML.

  • Extended Voxel Types

    Extended voxel types are all types that are composed of more than one component, e.g. complex, quaternion, vector or matrix types.

    There is a set of default extended types that is supported by some macros that are used to instantiate template methods for image calculation: std::complex<float>, std::complex<double>, Vector2f, Vector2d, Vector3f, Vector3d, Vector6f, Vector6d, Matrix2f, Matrix2d, Matrix3f and Matrix3d. Apart from these macro where these types are 'hardcoded', these types have no other special meaning.

  • Registered Voxel Types

    Registered voxel types are loaded to the application code on runtime. Each registered type provides a function table with functions for data addition, subtraction, multiplication, shift and so on. This table can be used to perform operations on this type.They also provide an MLTypeInfo data structure describing their properties, such as name, number of components, size, etc.

    The pre-registered types all have enumerators, type traits descriptions (via the TypeTraits template class) and type names that can be used in code directly.

7.5.2. Getting and Managing Metadata About Registered Voxel Types

The ML provides a number of functions to analyze, convert, process and manage a data type and its values as well as the components of these values.

These functions are useful for building modules that apply abstract operations on arbitrary data types, for example decomposing a voxel of any data type into its components or casting any arbitrary registered data type to another one.

[Note]Note

All these functions are part of the C-API of the ML. Hence they can also be used for managing voxel data in C programs or in modules that do not include the C++ API of the ML.

You do not have to distinguish between scalar and registered voxel data. The following functionality also works fine on scalar voxel types and data.

The most important functions

  • MLSizeOf (MLDataType dt),

  • MLGetDataTypeFromName (const char* dtName),

  • MLIsValidDataType (MLDataType dt),

  • MLIsScalarType (MLDataType dt),

  • MLGetTypeInfosForDataType (MLDataType dt), and

which are often used for module development, are described in Section 7.2.1, “Important Functions For Voxel Types”.

7.5.2.1. Functions for Managing Components of Registered Voxel Types

Functions for managing voxel components:

  • const char* MLGetCDataTypeNameForCharCode(char code);

    Returns the basic C/C++ data type name corresponding to a character representing it. On invalid codes "" is returned.

  • const char* MLGetMLDataTypeNameForCharCode(char code);

    Returns an ML type name compatible with a character representing it. On invalid codes "" is returned. The return value match for function calls to MLDataTypeFromName().

  • MLDataType MLGetMLDataTypeForCharCode(char code);

    Returns an ML data type compatible with a character representing it. On invalid codes -1 is returned.

  • MLint32 MLTypeGetComponentProperties(char code, MLint32* isSigned, MLint32* isIntegerType, MLint32* isFloatingPointType, MLint32* isLongType);

    Returns 1 (=true) in *isSigned, *isIntegerType, *isFloatingPointType and *isLongType if the component type represented by code includes this features, otherwise set that flag to 0 (=false). Invalid code values return 0 (=false) in all parameters. It is explicitly permitted to pass NULL as isSigned, isIntegerType, isFloatingPointType or isLongType to ignore these. 1 (=true) is returned if comp was a valid component, otherwise the return value is 0 (=false).

  • size_t MLTypeComponentSize(char comp);

    Returns the size of a MLTypeComponent denoted by a character code. On invalid character codes, 0 is returned. Valid codes are:

    • 'b' = bool

    • 'c' = unsigned char

    • 'C' = char

    • 's' = unsigned short

    • 'S' = short

    • 'i' = unsigned int

    • 'I' = int

    • 'l' = unsigned long

    • 'L' = long

    • '6' = MLint64

    • 'f' = float

    • 'd' = double

    • 'D' = long double

  • void MLTypeSetDoubleComponent(char comp, MLdouble val, MLTypeData *dstPtr);

    Interprets the data referenced by *dstPtr as data of the type comp and sets its value from the passed MLdouble value by casting the val to it. Invalid character codes are ignored.

  • void MLTypeSetIntComponent (char comp, MLCTInt val, MLTypeData *dstPtr);

    Same as MLTypeSetDoubleComponent, but components are set to integer values.

  • void MLTypeSetAllDoubleComponents(const MLTypeInfos *infos, MLdouble val, MLTypeData *dstPtr);

    All components of the data referenced by *dstPtr are set to a value cast from the MLdouble value val. Casting is performed by the MLTypeSetComponent function.

  • void MLTypeSetAllIntComponent (const MLTypeInfos *infos, MLCTInt val, MLTypeData *dstPtr);

    Same as MLTypeSetAllDoubleComponents, but components are set to integer values.

  • MLdouble MLTypeGetDoubleComponent(char comp, const MLTypeData *dstPtr);

    Interprets the data referenced by *dstPtr as data of the type comp and returns it as an MLdouble value. Invalid character codes are ignored and 0 is returned.

  • MLCTInt MLTypeGetIntComponent (char comp, const MLTypeData *dstPtr);

    Same as MLTypeGetComponent, but components are returned as integer values.

  • void MLTypeShiftLeftComponent(char comp, const MLTypeData *srcPtr, MLCTInt shiftLs, MLTypeData *dstPtr);

    Interprets the data referenced by *dstPtr as data of the type comp and shifts data shiftLs times to the left, if it is an integer component. Floating point components are multiplied with 2shiftLs. Negative values for shiftLs are interpreted as shift right operations or divisions by 2shiftLs, respectively. Boolean components become false on all shiftLs != 0. Zero shiftLs does not change values. Invalid character codes are ignored, i.e., pointers and values are not changed.

7.5.2.2. Convenience Functions to Operate on Registered Voxel Data

Functions to operate on data of registered voxels:

  • MLTypeData *MLAllocateVoxelBuffer(MLDataType dataType, size_t numVoxels, const MLTypeData *voxDefault);

    Returns a buffer of numVoxels voxels of data type dataType. On failure, NULL is returned. If voxDefault is NULL, the allocated memory is left undefined, otherwise all voxels are filled with the default value pointed to by voxDefault. The allocated buffer must be removed with MLFree().

  • char *MLGetVoxelValueAsString(const MLTypeData *data, MLDataType dataType, MLErrorCode *errCode);

    Interprets the data given by data as a value of type dataType and returns its value as a string. If anything fails, "" is returned. errCode may be passed as NULL. If errCode is not NULL, *errCode is set to the error code on failures; otherwise it is set to ML_RESULT_OK. Floating point values are normally printed with maximum precision. The returned pointer must be freed with MLFree().

  • char *MLGetVoxelValueAsStringLimited(const MLTypeData *data, MLDataType dataType, MLErrorCode *errCode, int maxPrec);

    Interprets the data given by data as a value of type dataType and returns its value as a string. If anything fails, "" is returned. errCode may be passed as NULL. If errCode is not NULL, *errCode is set to the error code on failures; otherwise it is set to ML_RESULT_OK. If maxPrec is passed with a negative value, the maximum precision of floating point numbers is printed. If passed >= 0, the number of digits is limited to maxPrec. It will be not larger than the maximum default precision, even when it is accordingly specified. The returned pointer must be freed with MLFree().

  • char *MLTypeComponentsToString(const MLTypeInfos *infos, const MLTypeData *p);

    Converts a data type instance to a string. infos point to the type information and p points to the data of the type instance. The return value is a string containing the type components which are converted to string values that are separated by spaces. It must be freed with MLFree(). Floating point values are normally printed with maximum precision. On failures (e.g. infos==NULL, p==NULL), an empty string is returned which also must be freed.

  • char *MLTypeComponentsToStringLimited(const MLTypeInfos *infos, const MLTypeData *p, int maxPrec);

    Converts a data type instance to a string. infos point to the type information and p points to the data of the type instance. The return value is a string containing the type components which are converted to string values that are separated by spaces. It must be freed with MLFree(). If maxPrec is passed with a negative value, the maximum precision of floating point numbers is printed. If passed >= 0, the number of digits is limited to maxPrec. It will not be larger than the maximum default precision even if specified so. On failures (e.g., infos==NULL, p==NULL), an empty string is returned which also must be freed.

  • MLint32 MLTypeComponentsFromString(const MLTypeInfos *infos, const char *str, const MLTypeData *defaultVal, MLTypeData *p);

    Converts a string of a data type instance to instance data, i.e., like an sscanf. infos point to the type information and p points to the data of the type instance to be filled with data scanned from the string. The return value is 1 if the string could be scanned successfully. On scan failures or invalid parameters, 0 is returned. If a default value is passed in defaultVal, components which could not be scanned correctly are copied from their corresponding positions in defaultVal. If defaultVal is passed as NULL, those components are left unchanged.

  • MLint32 MLTypeComponentsFromStream(void *iStr, void *iStrStream, void *stdiStr, void *stdiStrStream, const MLTypeInfos *infos, MLTypeData *data);

    Reads data type components into different stream versions (istream and istrstream within and outside the standard namespace). Since we have a C interface here, we need to pass the pointers to the streams as void* addresses. Hence be careful to which of the first parameters the stream is passed. All other can be set to NULL. On any error, *data is correctly set as far as possible, and all unreadable values are set to the default value. On bad parameters, failures or not completely readable values, 0 is returned, otherwise 1.

  • MLdouble MLGetVoxelValueAsDouble(const void *data, MLDataType dataType, MLErrorCode *errCode);

    Interprets the data given by data as a value of type dataType and return its value cast to double. If anything fails then 0 is returned. errCode may be passed as NULL. If errCode is not NULL then *errCode is set to the error code on failures; otherwise it is set to ML_RESULT_OK.

  • MLCTBool MLTypeCastToBool (const MLTypeInfos *infos, const MLTypeData *p);

    If p is identical to default element, false (= 0) is returned, otherwise true (= 1).

  • MLCTInt MLTypeCastToInt (const MLTypeInfos *infos, const MLTypeData *p);

    The first component of the data type p is converted to integer and returned.

  • MLdouble MLTypeCastToDouble (const MLTypeInfos *infos, const MLTypeData *p);

    The first component of the data type p is converted to double and returned.

  • void MLTypeCastFromBool (const MLTypeInfos *infos, MLCTBool p, MLTypeData *q);

    If p == 0 then q is set to the type default value given by infos. If p != 0 then all components of the type are cast to their values cast from 1.

  • void MLTypeCastFromInt (const MLTypeInfos *infos, MLCTInt p, MLTypeData *q);

    The integer value of p is cast to the types of the components and then written to them.

  • void MLTypeCastFromDouble (const MLTypeInfos *infos, MLdouble p, MLTypeData *q);

    The value p is cast to the types of the components and then written to them.

  • void MLTypeBinaryAndInt (const MLTypeInfos *infos, const MLTypeData *p, MLCTInt q, MLTypeData *r);

    Takes all components from p as integer values, applies a bitwise 'and' operation with q and writes them as (cast from) integer values back to the corresponding components of r.

  • void MLTypeBinaryOrInt (const MLTypeInfos *infos, const MLTypeData *p, MLCTInt q, MLTypeData *r);

    Takes all components from p as integer values, applies a bitwise 'or' operation with q and writes them as (cast from) integer values back to the corresponding components of r.

  • void MLTypeBinaryXorInt (const MLTypeInfos *infos, const MLTypeData *p, MLCTInt q, MLTypeData *r);

    Takes all components from p as integer values, applies a bitwise 'xor' operation with q and writes them as (cast from) integer values back to the corresponding components of r.

  • void MLTypeBinaryAnd (const MLTypeInfos *infos, const MLTypeData *p, const MLTypeData *q, MLTypeData *r);

    Takes all components from p as integer values, applies a bitwise 'and' operation with corresponding components from q (also as integers) and writes them as (cast from) integer values back to the corresponding components of r.

  • void MLTypeBinaryOr (const MLTypeInfos *infos, const MLTypeData *p, const MLTypeData *q, MLTypeData *r);

    Takes all components from p as integer values, applies a bitwise 'or' operation with corresponding components from q (also as integers) and writes them as (cast from) integer values back to the corresponding components of r.

  • void MLTypeBinaryXor (const MLTypeInfos *infos, const MLTypeData *p, const MLTypeData *q, MLTypeData *r);

    Takes all components from p as integer values, applies a bitwise 'xor' operation with corresponding components from q (also as integers) and writes them as (cast from) integer values back to the corresponding components of r.

  • void MLTypeShiftComponentsLeft(const MLTypeInfos *infos, const MLTypeData *p, MLCTInt q, MLTypeData *r);

    Takes one data type component after another and shifts each component left shiftLs times if it is an integer component. Floating point components are multiplied with 2shiftLs. Negative values for shiftLs are interpreted as shift right operations or divisions by 2shiftLs, respectively. Boolean components become false on all shiftLs != 0. Zero shiftLs does not change any component.

  • void MLTypeCastToOtherType(const MLTypeInfos *otherInfos, const MLTypeData *otherData, const MLTypeInfos *myInfos, MLTypeData *myData);

    Converts a data instance referenced by otherData of a type specified by otherInfos to another data instance referenced by *myData of a type specified by myInfos. As long as components of any data type in the source exist, the myData components are set to the same values. Components which do not have a counterpart in the otherData are filled with the counterparts from its default value given by the myInfos. E.g.: If an (int, char, double) data type (represented by "ICd") is cast to a four component float vector (represented by "ffff"), then the first three components are set from an int cast to double, from an char cast to double and from an double cast to double. The fourth component is copied from the fourth component of the type default value given in the dstInfos of type MLTypeInfo.

  • void MLTypeCastFromOtherType(const MLTypeInfos *otherInfos, const MLTypeData *otherData, const MLTypeInfos *myInfos, MLTypeData *myData);

    Casts another data element otherData with attributes given by otherInfos to myData of a type given by myInfos. See MLTypeCastToOtherType for more infos.

  • MLint32 MLTypeIsEqualToOtherType(const MLTypeInfos *myInfos, const MLTypeData *myData, const MLTypeInfos *otherInfos, const MLTypeData *otherData);

    Casts another data element otherData with attributes given by otherInfos to a local buffer of a type given by myInfos. If that buffer is equal to myData then 1 (=true) is returned, otherwise 0 (=false). For the comparison myInfos->isEqualToType is used.

  • MLint32 MLTypeIsSmallerThanOtherType(const MLTypeInfos *myInfos, const MLTypeData *myData, const MLTypeInfos *otherInfos, const MLTypeData *otherData);

    Casts another data element otherData with attributes given by otherInfos to a local buffer of a type given by myInfos. If that buffer is smaller than myData then 1 (=true) is returned, otherwise 0 (=false). For the comparison myInfos->isSmallerThanType is used.

  • MLint32 MLTypeIsGreaterThanOtherType(const MLTypeInfos *myInfos, const MLTypeData *myData, const MLTypeInfos *otherInfos, const MLTypeData *otherData);

    Casts another data element otherData with attributes given by otherInfos to a local buffer of a type given by myInfos. If that buffer is greater to myData then 1 (=true) is returned, otherwise 0 (=false). For the comparison myInfos->isGreaterThanType is used.

  • void MLTypeMultWithOtherType(const MLTypeInfos *myInfos, const MLTypeData *myData, const MLTypeInfos *otherInfos, const MLTypeData *otherData, MLTypeData *r);

    Casts another data element otherData with attributes given by otherInfos to a local buffer of a type given by myInfos. That buffer is multiplied with myData and written into r. For the multiplication myInfos->multWithType is used.

  • void MLTypeDivByOtherType(const MLTypeInfos *myInfos, const MLTypeData *myData, const MLTypeInfos *otherInfos, const MLTypeData *otherData, MLTypeData *r);

    Casts another data element otherData with attributes given by otherInfos to a local buffer of a type given by myInfos. Then myData is divided by the buffer and written into r. For the division myInfos->divByType is used.

  • void MLTypeAddOtherType(const MLTypeInfos *myInfos, const MLTypeData *myData, const MLTypeInfos *otherInfos, const MLTypeData *otherData, MLTypeData *r);

    Casts another data element otherData with attributes given by otherInfos to a local buffer of a type given by myInfos. That buffer is added with myData and written into r. For the addition myInfos->addToType is used.

  • void MLTypeSubtractOtherType(const MLTypeInfos *myInfos, const MLTypeData *myData, const MLTypeInfos *otherInfos, const MLTypeData *otherData, MLTypeData *r);

    Casts another data element otherData with attributes given by otherInfos to a local buffer of a type given by myInfos. That buffer is subtracted from myData and written into r. For the subtraction myInfos->subtractFromType is used.

  • void MLTypePowerOfOtherType(const MLTypeInfos *myInfos, const MLTypeData *myData, const MLTypeInfos *otherInfos, const MLTypeData *otherData, MLTypeData *r);

    Casts another data element otherData with attributes given by otherInfos to a local buffer of a type given by myInfos. The power of myData with the buffer is calculated and written into r. For the power calculation myInfos->powerOfType is used.

7.5.3. Reducing Generated Code and Compile Times

Sometimes a module programmer knows that a module only makes sense for images with certain voxel types. In this case, the number of potential voxel types can be reduced so that the code is smaller and the compilation times are shortened.

Typical application areas are binary operations on voxels which only work fine on integer voxels; or operations on normalized values which are always between 0 and 1 and consequently require floating point type voxels to avoid information loss. Also, some operations such as gradient calculations or tensor imaging might require operations which only make sense on registered vector or matrix voxels.

A typical ML module uses a ML_CALCULATE_OUTPUTSUBIMAGE macro to compile the template calculateOutputSubImage function for all scalar types:

  // Implements the call  to the typed calculateOutputSubImage method for all potential data types.
  ML_CALCULATEOUTPUTSUBIMAGE_NUM_INPUTS_1_SCALAR_TYPES_CPP(NormalModule);

  //! Fill output page with calculated data in a module with one input.
  template <typename DATATYPE>
  void NormalModule::calculateOutputSubImage(TSubImage<DATATYPE> *outSubImg,
                                     int outIndex, TSubImage<DATATYPE> *inSubImg)
  {
    // Calculate contents of outSubImg here.
  }

The following example shows how to compile the template function for all available integer types only. It uses a special ML_CALCULATE_OUTPUTSUBIMAGE macro which accepts an additional parameter to determine the set of data type cases to be compiled:

  // Implements the call  to the typed calculateOutputSubImage method for all integer types.
  ML_CALCULATEOUTPUTSUBIMAGE_NUM_INPUTS_1_WITH_CUSTOM_SWITCH_CPP(CalcTest, ML_IMPLEMENT_INT_CASES);

  //! Fill output page with calculated data in a module with one input.
  template <typename DATATYPE>
  void NormalModule::calculateOutputSubImage(TSubImage<DATATYPE> *outSubImg,
                                     int outIndex, TSubImage<DATATYPE> *inSubImg)
  {
    // Calculate contents of outSubImg here.
  }

The following predefined type configurations of data type cases can be used:

  • ML_IMPLEMENT_INT_CASES - Implements all integer types.

  • ML_IMPLEMENT_FLOAT_CASES - Implements all floating point types.

  • ML_IMPLEMENT_INT_FLOAT_CASES - Implements all integer and floating point types.

  • ML_IMPLEMENT_INT_FLOAT_CASES_WO_INT64 - Implements all integer and floating point types without the 64 bit integer types.

  • ML_IMPLEMENT_COMPLEX_CASES - Implements the complex types.

  • ML_IMPLEMENT_VECTOR_CASES - Implements the default vector types: Vector2, Vector3 and Vector6 (both with float and double component types).

  • ML_IMPLEMENT_MATRIX_CASES - Implements the default matrix types: Matrix2 and Matrix3 (both with float and double component types).

  • ML_IMPLEMENT_SCALAR_CASES - Identical to ML_IMPLEMENT_INT_FLOAT_CASES which implements the scalar voxel types used in most ML modules.

  • ML_IMPLEMENT_DEFAULT_CASES - This combines ML_IMPLEMENT_SCALAR_CASES, ML_IMPLEMENT_COMPLEX_CASES, ML_IMPLEMENT_VECTOR_CASES and ML_IMPLEMENT_MATRIX_CASES.

You can also configure your own combinations using the following constants:

  • ML_IMPLEMENT_INT8_CASE and ML_IMPLEMENT_UINT8_CASE - Signed and unsigned 8 bit integers.

  • ML_IMPLEMENT_INT16_CASE and ML_IMPLEMENT_UINT16_CASE - Signed and unsigned 16 bit integers.

  • ML_IMPLEMENT_INT32_CASE and ML_IMPLEMENT_UINT32_CASE - Signed and unsigned 32 bit integers.

  • ML_IMPLEMENT_INT64_CASE - Signed 64 bit integer.

  • ML_IMPLEMENT_FLOAT_CASE, ML_IMPLEMENT_DOUBLE_CASE - Floating point types.

In the following example only the MLint64, the MLdouble and all complex types are compiled:

#define ML_IMPLEMENT_LARGE_AND_COMPLEX_CASES(CLASS_NAME, SWITCH_CODE, DUMMY1, DUMMY2, DUMMY3)  \
    ML_IMPLEMENT_INT64_CASE(     CLASS_NAME, SWITCH_CODE, DUMMY1, DUMMY2, DUMMY3) \
    ML_IMPLEMENT_DOUBLE_CASE(    CLASS_NAME, SWITCH_CODE, DUMMY1, DUMMY2, DUMMY3) \
    ML_IMPLEMENT_COMPLEX_CASES(  CLASS_NAME, SWITCH_CODE, DUMMY1, DUMMY2, DUMMY3)

  ML_CALCULATEOUTPUTSUBIMAGE_NUM_INPUTS_1_WITH_CUSTOM_SWITCH_CPP
    (CalcTest, ML_IMPLEMENT_LARGE_AND_COMPLEX_CASES);

  template <typename DATATYPE>
    void NormalModule::calculateOutputSubImage(TSubImage<DATATYPE> *outSubImg,
                                       int outIndex,
                                       TSubImage<DATATYPE> *inSubImg){ ... }

7.5.4. Configuration of Supported Voxel Types

There are some advanced options you can use when you activate the support of your ML module for registered data types.

  • setVoxelDataTypeSupport(ONLY_DEFAULT_TYPE) lets the module work with the default type set supported by macros like ML_CALCULATEOUTPUTSUBIMAGE_NUM_INPUTS_1_DEFAULT_TYPES_CPP. So if you use a module macro with DEFAULT in its name, use this value.

  • setVoxelDataTypeSupport(ONLY_SCALAR_TYPES) is the default setting which causes the ML to deactivate all modules outputs if any of the voxel data is of non-scalar type. This stops a module from operating on non-scalar data types, so that a module programmer does not have to care about them at all.

  • setVoxelDataTypeSupport(ALL_REGISTERED_TYPES) is the mode to have the module work with all registered voxel types which do not provide all operations of a standard type.

    [Note]Note

    A module can always restrict the types it supports in the calculateOutputImageProperties method by setting an output to invalid if a type combination is not supported.

    This mode is useful for modules which handle or just pass voxel data, but do not calculate explicit output values, like for example a SubImage module. It is also useful for modules which support types other than the scalar types when they are not using the module macro for the default type set, e.g. when a module is implemented through typed output handlers.

7.5.5. Implementing a New Voxel Data Type by Deriving from MLTypeInfos

You can register your own voxel data type. A structure that describes the data type, its properties and a function table with its operations can be registered in the ML to activate a new type. Modules that perform generic operations that use the registered type structures (directly or indirectly) will automatically work with this new type.

There are some steps to take and many functions to implement, but generally it is not really difficult and does not involve as much work as one might think. The easiest way is to use the MLTypeAddExampleInfos example as a template for integrating a new type; this way you will not forget any important steps. See MLTypeAddExample for detailed information.

  1. Take an MLTypeInfos structure from mlTypeDefs.h .

  2. Set all function pointers in that structure to functions that implement your data type operations.

  3. Initialize your MLTypeInfos (Section 7.5.5.1, “Describing a New Voxel Type with MLTypeInfos”) (see mlTypeDefs.h ) by using MLTypeInfosInit() (see mlDataTypes.h ).

  4. Create one instance of your initialized MLTypeInfos and register it on library initialization by using MLRegisterTypeInfos() from mlDataTypes.h .

The result of your implementation should be an initialized MLTypeInfos structure that describes all data type properties, features and operations (see Section 7.5.5.1, “Describing a New Voxel Type with MLTypeInfos”). Take a closer look at this structure now.

7.5.5.1. Describing a New Voxel Type with MLTypeInfos

The MLTypeInfos structure describes all features, properties and operations of a data type. It contains all data type features and pointers to all functions needed to implement generic operations on the data type. It is a wrapper for any information and code needed for any standard or user-defined data type the ML uses. The descriptions of the scalar ML data types are implemented in .

The descriptive components of the MLTypeInfos structure are permanent and non-changing values requested by many operations that need data type information. Most of these values can be initialized with the function MLTypeInfosInit() which also performs checks for valid data type initialization and calculates the more difficult components of the MLTypeInfos structure. All other stuff (i.e., the function pointers) should be initialized by using the macro ML_TYPE_ASSIGN_FUNCTION_POINTERS() as explained in the example in Section 7.5.5.2, “The MLTypeAddExample”.

  1. size_t numComps

    Number of components of this data type. Equals number of characters in *structInfoString. A scalar value has 1 component, complex numbers have 2 components, and ML vectors have 6 components (see Section 2.4.1, “ImageVector, ImageVector” for details) Each component must be a scalar object as described in number 13.

  2. size_t typeSize

    The sizeof of the registered data type, i.e., its size in bytes.

  3. const char *name

    The pointer to a null-terminated character string that gives the data type name. It should contain alphanumeric characters only.

  4. MLDataType rangeAndPrecisionEquivalent

    Returns a standard data type which has a comparable range and precision behavior.

  5. double dblMin

    Double minimum of data type.

  6. double dblMax

    Double maximum of data type.

  7. const MLTypeData *typeMinPtr

    Minimum value of the data type.

  8. const MLTypeData *typeMaxPtr

    Maximum value of the data type.

  9. const MLTypeData *typeDefaultPtr

    The default value of the data type, comparable to zero.

  10. size_t numGoodCastTos

    Number of data types to which this type can be cast without information or functionality loss.

  11. const char **goodCastTos

    Pointer to a table of a null-terminated string of data type names to which this type can be cast without information or functionality loss.

  12. unsigned int compOffsets[ML_MAX_COMPONENTS_EXTENDED_TYPE]

    Table of byte offsets from the first component to other components to directly address any component with a character pointer. e.g., if a data type consists of a float, a char and another float component where sizeof(float) is 4 and sizeof(char) is 1, the first table entry must be 0, the second entry must be 4, and the third entry must be 5.

  13. const char *structInfoString

    Pointer to a null-terminated string that describes the type configuration as explained for the typeStructInfo parameter of the function MLTypeInfoInit .

  14. int dataTypeId

    The MLDataType id of the registered type. This should be a constant value. If you want to define your own types you should contact the MeVisLab team to get your own id range assigned.

The operative components of the MLTypeInfos structure are function pointers which are called when operations on a registered data type are needed. We will forgo the opportunity to list all functions here, simply refer to the definition of MLTypeInfos in mlTypeDefs.h for the required functions.

Many operations can simply be implemented by using convenience functions which are already implemented in the ML, e.g. to cast one extended data type to another.

The parameters of these functions are often pointers of type MLTypeData to instances of the data type; the parameters need to be cast to be able to work on the correct data type.

The MLTypeInfosInit() function checks for valid data type initialization, and calculates the more difficult components of the MLTypeInfos structure. It returns 1(=true) on success, 0(=false) on failure.

Please refer to the MLTypeAddExample example on how exactly to register your own type.

7.5.5.2. The MLTypeAddExample

The following example shows how to implement a new voxel type. The example does not implement all functions in order to keep the example short. However, the implementation of most functions should not be a problem when you look at similar functions for reference.

[Note]Note
  • Many functions are implemented by using ML functions; they typically implement the desired operation for each components of the new data type. Thus, especially vector operations can often be implemented easily. See the header file documentation of those functions for detailed descriptions.

  • Most functions get pointers to the data instances by const MLTypeData or MLTypeData pointers. This is necessary because the functions are defined generically and don't know the real type. Thus, many casts of those pointers are needed before the actual type operations can be applied.

  • Do not change the function names because it is exactly these names that are used in the ML_TYPE_ASSIGN_FUNCTION_POINTERS() macros to set the members in the MLTypeInfos structure. Thus, missing functions will also be detected which makes sure that no function is forgotten.

The initialization of the new voxel data type, typically to be implemented in the library initialization file:

Example 7.9.  MLRegisterTypeInfos

//! Create static instances of all data types to be used in the ML.
//! These instances will directly be registered as new ML data types.
static MLTypeAddExampleInfos _MLNewVType;
int initResult = MLRegisterTypeInfos(&_MLNewVType);
return initResult;

The implementation of the MLTypeAddExampleInfos class which is used to create the registered instance in the library initialization file:

Example 7.10. How to Add Your Own Voxel Data Type

This example is outdated. Please refer to the example code provided with the MeVisLab SDK for the current version.

#ifndef __mlMLGuideTypeAddExampleInfos_H
#define __mlMLGuideTypeAddExampleInfos_H

// ML-includes
#ifndef __MLTypeAddExampleSystem_H
#include "MLTypeAddExampleSystem.h"
#endif
#ifndef __mlDataTypes_H
#include "mlDataTypes.h"
#endif
#ifndef __mlUtils_H
#include "mlUtils.h"
#endif

ML_START_NAMESPACE

//! The data type to be implemented as a new voxel data type.
//! For simplification we register a new type which does the
//! same as the normal MLdouble type.
typedef MLdouble NewVType;

//--------------------------------------------------------------------------------------
//! Example class to create a new voxel data types to be registered in the ML.
//--------------------------------------------------------------------------------------
class ML_NEW_VTYPEEXTENSION_EXPORT MLTypeAddExampleInfos : public MLTypeInfos {

protected:

  //! Reference to a permanently existing constant instance of NewVType containing the
  //! minimum data type value.
  static const NewVType &_typeMin()    { static NewVType v=-DBL_MAX; return v; }

  //! Reference to a permanently existing constant instance of NewVType containing the
  //! maximum data type value.
  static const NewVType &_typeMax()    { static NewVType v=DBL_MAX;  return v; }

  //! Reference to a permanently existing constant instance of NewVType containing the
  //! default data type value.
  static const NewVType &_typeDefault(){ static NewVType v=0;        return v; }

  //! Permanent instance of a pointer to the typeInfos used by this class. It
  //! will often be used as a kind of this pointer for the static instance of
  //! this data type information.
  static MLTypeInfos *_myInfos;

  //! Number of instances of this class. Only used to avoid that more than one
  //! instance is created.
  static size_t _numInstances;
public:

  //! Constructor. It initializes
  //! - all data type operations by setting pointers of a
  //!   function table to the data type operations (implemented
  //!   as static functions) by using the macro
  //!   ML_TYPE_ASSIGN_FUNCTION_POINTERS();
  //! - all other data type properties, like min/max/default values (as
  //!   MLdouble and as type values) by using the function MLTypeInfosInit(),
  //! - it checks for at most one instance of this class.
  MLTypeAddExampleInfos()
  {
    // We permit only one instance since most class settings are static constant.
    if (_numInstances > 0){
      mlError("MLTypeAddExampleInfos", ML_PROGRAMMING_ERROR)
        << "Too many instances of MLTypeAddExampleInfos created.";
    }
    _numInstances++;

    // Store pointer to this. We only have one instance. So we simulate a kind
    // "this" pointer for this static instance.
    _myInfos = this;

    // Assign all pointers to the static functions implementing the operations.
    // The function names have predefined names beginning with "MLTYPE_" (see
    // function implementation below).
    ML_TYPE_ASSIGN_FUNCTION_POINTERS();
    // Specify all type names and their number to which this type can be cast
    // without information loss.
    size_t numGoodCastTos = 1;
    static const char *goodCastTos[] = { "NewVType" };

    // Initialize the new MLTypeInfos struct. For a parameter description see
    // discussion of MLTypeInfos structure or the type documentation in the
    // mlTypeDefs.h file.
    NewVType buf;
    void *addr[1];
    addr[0] = &buf;
    MLTypeInfosInit(this,
                    sizeof(NewVType),
                    "NewVType",
                    -DBL_MAX,
                    DBL_MAX,
                    (MLTypeData*)(&_typeMin()),
                    (MLTypeData*)(&_typeMax()),
                    (MLTypeData*)(&_typeDefault()),
                    "d",
                    false,
                    MLdoubleType,
                    addr,
                    numGoodCastTos,
                    goodCastTos
                    );
  }
  //! Return value as string to be freed by MLFree().
  //! Use MLTypeComponentsToString() if possible.
  static char *MLTYPE_getStringValue(const MLTypeData *p)
  { return MLTypeComponentsToString(_myInfos, p); }

  //! Convert string s to value and write result into r.
  //! Use MLTypeComponentsFromString() if possible.
  static void MLTYPE_setStringValue(const char *s, MLTypeData *r)
  { MLTypeComponentsFromString(_myInfos, s, (MLTypeData*)&(_typeDefault()), r);  }

  // IMPLEMENT MINIMUM/MAXIMUM/DEFAULT AND COPY OPERATIONS.
  //! Sets p to minimum value. Must be implemented.
  static void MLTYPE_setToMinimum(MLTypeData *p)
  { memcpy(p, &_typeMin(), sizeof(NewVType)); } 
  //! Sets p to minimum value. Must be implemented.
  static void MLTYPE_setToMaximum(MLTypeData *p)
  { memcpy(p, &_typeMax(), sizeof(NewVType)); }

  //! Sets p to default value. Must be implemented.
  static void MLTYPE_setToDefault(MLTypeData *p)
  { memcpy(p, &_typeDefault(), sizeof(NewVType)); }

  //! Copy parameter p to second q.
  static void MLTYPE_copy(const MLTypeData *p, MLTypeData *q)
  { memcpy(q, p, sizeof(NewVType)); } 
  // IMPLEMENT CAST OPERATIONS FROM THE NEW TYPE TO BOOL/INT/DOUBLE/OTHER TYPE.
  //! Return parameter p cast to bool. Typically false when it is identical to
  //! the default element, otherwise true.
  static MLCTBool MLTYPE_castToBool(const MLTypeData *p)
  { return (*((NewVType*)p)) != _typeDefault(); }

  //! Return parameter p cast to integer. Often implemented as
  //! the integer cast of the first component.
  static MLCTInt MLTYPE_castToInt(const MLTypeData *p)
  { return    (MLCTInt)( *((NewVType*)p) ); }

  //! Return parameter p cast to MLdouble. Often implemented as
  //! the integer cast of the first component.
  static MLdouble MLTYPE_castToDouble(const MLTypeData *p)
  { return (MLdouble)( *((NewVType*)p) ); }

  //! Cast myData to otherData who has type infos otherInfos. Usually
  //! implemented by default with function casting componentwise.
  static void MLTYPE_castToOtherType(const MLTypeData *myData,
                                     const MLTypeInfos *otherInfos,
                                     MLTypeData *otherData)
  { MLTypeCastToOtherType(_myInfos, myData,  otherInfos, otherData); }
  // IMPLEMENT CAST OPERATIONS FROM INT/DOUBLE/OTHER TYPE TO THE NEW TYPE.
  //! Cast first parameters to data type and write it into second parameter.
  static void MLTYPE_castFromInt(MLCTInt p, MLTypeData *q)
  { *((NewVType*)q) = (NewVType)p;  }

  //! Cast first parameters to data type and write it into second parameter.
  static void MLTYPE_castFromDouble(MLdouble p, MLTypeData *q)
  { *((NewVType*)q) = (NewVType)p;  }

  //! Cast first parameters to data type and write it into second parameter.
  static void MLTYPE_castFromOtherType(const MLTypeInfos *otherInfos,
                                       const MLTypeData *otherData,
                                       MLTypeData *myData)
  { MLTypeCastToOtherType(otherInfos,otherData, _myInfos, myData); }
  static MLCTBool MLTYPE_isEqualToType(const MLTypeData *p, const MLTypeData *q)
  { return (*((NewVType*)p)) == (*((NewVType*)q)); }

  // IMPLEMENT SOME SPECIAL FUNCTIONS
  //! Negate the value.
  static void MLTYPE_negate(const MLTypeData *p, MLTypeData *q)
  { *((NewVType*)q) = -(*((NewVType*)p)); }

  //! Normalize type.
  static void MLTYPE_normalize (const MLTypeData * /*p*/, MLTypeData *q)
  { *((NewVType*)q) = (NewVType)(1); }
  // IMPLEMENT MULTIPLICATION FUNCTIONS. THE RESULT IS WRITTEN ALWAYS INTO LAST
  // FUNCTION PARAMETER R.
  //! Implement multiplication with integer. Result written into parameter three.
  static void MLTYPE_multWithInt(const MLTypeData *p, MLCTInt q, MLTypeData *r)
  { *((NewVType*)r) = (*((NewVType*)p)) * (NewVType)q; }

  //! Implement multiplication with double. Result written into parameter three.
  static void MLTYPE_multWithDouble(const MLTypeData *p, MLdouble q, MLTypeData *r)
  { *((NewVType*)r) = (*((NewVType*)p)) * (NewVType)q; }

  //! Implement multiplication with its own type. Result written into parameter three.
  static void MLTYPE_multWithType(const MLTypeData *p, const MLTypeData *q, MLTypeData *r)
   { *((NewVType*)r) = (*((NewVType*)p)) * (*((NewVType*)q)); }

  //! Implement multiplication with another type. Result written into parameter three.
  static void MLTYPE_multWithOtherType(const MLTypeInfos *otherInfos,
                                       const MLTypeData  *otherData,
                                       const MLTypeData  *myData,
                                       MLTypeData        *r)
  { MLTypeMultWithOtherType(_myInfos, myData, otherInfos, otherData, r); }
  //! IMPLEMENT ADDITIONS. SEE MULTIPLICATION FUNCTIONS FOR SIMILAR CODE.
  static void MLTYPE_plusInt   (const MLTypeData *p, MLCTInt q, MLTypeData *r)  { /*...*/ }
  static void MLTYPE_plusDouble(const MLTypeData *p, MLdouble q, MLTypeData *r) { /*...*/ }
  static void MLTYPE_plusType  (const MLTypeData *p, const MLTypeData *q, MLTypeData *r) { /*...*/ }