ML Reference
CarrierTypes removed

Rationale

With MeVisLab 2.3 we decided to abandon the concept of the CarrierTypes.

MeVisLab allows to process images of non-scalar voxel types like complex numbers, vectors or matrices (called extended types by us).

Previously we had a mechanism in place that allowed to use all the common C++ operators on any of the registered extended types by wrapping them in so-called CarrierTypes of certain predefined sizes, which operated through tables of operations on the currently selected type. This allowed to write image algorithms on all registered voxel types by using templated methods which were instantiated for all scalar and all CarrierTypes - usually the instantiation was done through some multiplexing C macros provided by the ML. This was fairly easy, but had some drawbacks when you really needed to support extended types:

  • The carrier types only came in certain sizes, which meant you could waste memory on types like Matrix3.
  • When iterating over them you had to remember that your voxels were not necessarily spaced directly adjacent in memory because of this.
  • Many operations provided by the CarrierTypes had no well-defined meaning for the underlying types.
  • The global type operation tables needed to be switched when operating on different extended types, which is a burden in multi-threaded environments.
  • The general purpose multiplexing macros often were used even when only scalar types needed to be supported and thus a lot of unneeded binary code was created.

So we decided to abandon the CarrierTypes.

What has changed?

Some functions that had to do with handling of CarrierTypes have been removed or simply don't do anything anymore.

ML modules that only work on scalar voxel values probably won't need adaptation. The old multiplexing macros now default to only support the scalar types, but are deprecated in favor of macros with a name that better reflects the changed semantics. We also changed the arguments to ml::Module::setVoxelDataTypeSupport. The old enum values still work, but map to new values with sometimes different meaning. Please consult the documentation, especially if you used this method with the values FULLY_OPERATIONAL, MINIMUM_OPERATIONAL or even PARTIALLY_OPERATIONAL.

There are some new macros that additionally support some selected default extended types, but chances are that these don't fit your needs. We recommend that module authors that need to support selected extended types switch to the new typed output handlers, which have far more flexibility in what input and output types they support. There are some examples in the documentation for the class ml::TypedCalculateOutputImageHandler. Look also at the end of the file mlTypedHandlers.h for an example on how to define your own set of types. If you are in doubt on how to use this, don't hesitate to ask us at the MeVisLab forum; we acknowledge that this is slightly more complex than before.

Another challenge that arises from this change is when compiling code that supports scalar and extended types at the same time. The CarrierTypes had all the operators defined that the scalar types have, so compiling was no problem. With the real extended types the compiler will complain over each missing operator or conversion, so one needs to differentiate the code paths for scalar types and extended types. A simple "if" will not work for this, since the compiler will try to compile both code paths, even if the condition can be evaluated at compile-time. The solution for such problems can be found in mlTypeTraits.h, either in the specialized casts and the derived types provided by ml::TypeTraits or by using the OverloadSelector functions to switch to different function implementations depending on a template type.

This boils down to either using code like this:

typedef typename TypeTraits<DATATYPE>::IntermediateType IntermediateType;

// IntermediateType is double for scalar types and
// DATATYPE itself for all other types:
IntermediateType retVal = ml_cast_from_scalar<IntermediateType>(0);

for (size_t c=0; c < indexTabSize; c++){
  retVal += static_cast<IntermediateType>(inCursor[indexTab[c]]) * 
            ml_scalar_factor_cast<IntermediateType>(valTab[c]);
}

// this cast does rounding _and_ clamping for the built-in integer types:
*outCursor = ml_cast_from_intermediate_type<DATATYPE>(retVal);

or like this:

// function for handling scalar voxel types
template<DATATYPE>
void doKernel(DATATYPE* inCursor, DATATYPE* outCursor, ..., OverloadSelector::OnTrue) {
  MLdouble retVal  = 0.0;

  for (size_t c=0; c < indexTabSize; c++){
    retVal += (inCursor[indexTab[c]] * valTab[c]);
  }

  *outCursor = static_cast<DATATYPE>(retVal);
}

// code for handling non-scalar voxel types
template<DATATYPE>
void doKernel(DATATYPE* inCursor, DATATYPE* outCursor, ..., OverloadSelector::OnFalse) {
  DATATYPE retVal  = 0;

  for (size_t c=0; c < indexTabSize; c++){
    retVal += static_cast<DATATYPE>(inCursor[indexTab[c]] * valTab[c]);
  }

  *outCursor = retVal;
}

...

// this call will call different functions, depending on what type DATATYPE is:
doKernel(inCursor, outCursor, ..., OverloadSelector::isScalarType<DATATYPE>());

long double isn't supported as voxel type anymore

We also decided to not support the long double voxel type anymore. Under Windows long double only had normal double precision anyway, because we never had activated the correct compiler flags, so we assume that nobody really used that as voxel data type. This change makes the ML interface simpler at many places, but some old modules might need to be switched from long double to double variables at some places to avoid warnings or compile errors.