15.2. Developing the MLBaseOwner Module and the BaseMessenger Class

The ML module is of the class Module which is the base class from which all C++-based image processing modules are derived. Usually, it is used to implement new algorithms for processing voxel images. In our example, it will only offer the fields to parameterize the simple scene.

The class Module is explained in more detail in the ML Guide, chapter Deriving Your Own Module from Module.

Technically, this module owns the object derived from Base we will implement later:

15.2.1. Creating the BaseCommunication Project in the Wizard

To create the MLBaseOwner module in the BaseCommunication project, use the wizard.

First of all, make sure that you have a user package defined as described in Section 8.2, “Creating a User Package for Your Project” or create it now. Then run the Project Wizard and select the option ML Module. This starts the Wizard for C++/ML Modules.

  1. On the dialog Module Properties, enter the following:

    • Name: BaseOwner

    • Comment: Module for setting parameters of a Base object via fields

    • Keyword: Example

    • See Also: SoBaseReceiver

    • Target Package: your package, for example “Example/General

    • Project: BaseCommunication

    Figure 15.5. Project Wizard — Module Properties

    Project Wizard — Module Properties

    The project name is different to the module name here because the project will later include the module MLBaseOwner and the additional class BaseMessenger.

    Click Next to proceed.

  2. On the dialog Imaging Module Properties, all settings have to be removed as the module has no image input/outputs.

    • Keep New Style ML Module, as the setting is irrelevant for our example.

    • Change the settings to 0 inputs and 0 outputs.

    • Uncheck all options (Add calculateInputSubImageBox and Add voxel loop to calculateOutputSubImage).

    Figure 15.6. Project Wizard — Imaging Module Properties

    Project Wizard — Imaging Module Properties

    Click Next.

  3. On the dialog Additional Module Properties, the following settings are necessary:

    • Check Add activateAttachements().

    • Check Add MDL window with fields.

    • Uncheck everything else.

    Figure 15.7. Project Wizard — Additional Module Properties

    Project Wizard — Additional Module Properties

    Click Next.

  4. On the dialog Module Field Interface, add the following five fields (their sequence is not important):

    • Enter field:

      • Field Name: outputMessenger

      • Field Type: Base

      • Field Comment: Output of the Base object holding the parameters for the Inventor scene.

    • Enter field:

      • Field Name: shapeType

      • Field Type: Enum

      • Field Comment: Selects the type of the rendered shape.

      • Field Value: 0

      • Enum Values: Cube, Sphere

    • New field:

      • Field Name: translation

      • Field Type: Vector3

      • Field Comment: The translation of the rendered shape.

    • New field:

      • Field Name: color

      • Field Type: Color

      • Field Comment: The color of the rendered shape.

    • New field:

      • Field Name: diameter

      • Field Type: Double

      • Field Comment: The diameter of the rendered shape.

      • Field Value: 1

    Figure 15.8. Project Wizard — Module Field Interface

    Project Wizard — Module Field Interface

  5. Click Create to create the project.

    In the default file browser of your system, two folders are opened:

    • folder with the source code: {packagePath}\Sources\ML\MLBaseCommunication

    • folder with the module's GUI definition: {packagePath}\Modules\ML\MLBaseCommunication

    [Note]Note

    For a full list of all created files and their contents, see the MeVisLab Reference Manual, chapter ML Module (Wizard).

  6. Close the Wizard.

The code resulting from the wizard is:

//----------------------------------------------------------------------------------
//! The ML module class BaseOwner.
/*!
// \file   
// \author  JDoe
// \date    2016-03-03
//
// Module for setting parameters of a Base object via fields.
*/
//----------------------------------------------------------------------------------

#include "mlBaseOwner.h"


ML_START_NAMESPACE

//! Implements code for the runtime type system of the ML
ML_MODULE_CLASS_SOURCE(BaseOwner, Module);

//----------------------------------------------------------------------------------

BaseOwner::BaseOwner() : Module(0, 0)
{
  // Suppress calls of handleNotification on field changes to
  // avoid side effects during initialization phase.
  handleNotificationOff();

  // Add fields to the module and set their values.
  _outputMessengerFld = addBase("outputMessenger", NULL);
  static const char * const shapeTypeValues[] = { "Cube", "Sphere" };
  _shapeTypeFld = addEnum("shapeType", shapeTypeValues, 2);
  _shapeTypeFld->setEnumValue(0);
  _translationFld = addVector3("translation", Vector3());
  _colorFld = addColor("color", 1,1,1);
  _diameterFld = addDouble("diameter", 1);

  // Reactivate calls of handleNotification on field changes.
  handleNotificationOn();
}

//----------------------------------------------------------------------------------

void BaseOwner::handleNotification(Field* field)
{
  // Handle changes of module parameters and input image fields here.
}

//----------------------------------------------------------------------------------

void BaseOwner::activateAttachments()
{
  // Update members to new field state here.
  // Call super class functionality to enable notification handling again.
  Module::activateAttachments();
}

ML_END_NAMESPACE

15.2.2. Adding New Files

Open your folder {packagePath}/Sources/ML/MLBaseCommunication and add two empty files:

  • BaseMessenger.h

  • BaseMessenger.cpp

These will be used for the BaseMessenger class that transmits the field values from the ML module to the Open Inventor module.

15.2.3. Adding References to the new Files in CMakeLists.txt

  1. Open CMakeLists.txt of the project in a text editor.

  2. Add references to the new files. Result:

    target_sources(MLBaseCommunication PRIVATE
        BaseMessenger.cpp
        BaseMessenger.h
        MLBaseCommunicationInit.cpp
        MLBaseCommunicationInit.h
        MLBaseCommunicationSystem.h
        mlBaseOwner.cpp
        mlBaseOwner.h
    )
  3. Add the include paths for Base objects (MLBase) to the configuration:

    find_package(MeVisLab COMPONENTS ML MLBase HINTS "$ENV{MLAB_ROOT}" REQUIRED)
    target_link_libraries(MLBaseCommunication
      PUBLIC
        MeVisLab::ML
        MeVisLab::MLBase
    )
  4. Compile the CMakeLists.txt file and open the project in your development environment.

15.2.4. Adding Contents to BaseMessenger.h

Open BaseMessenger.h and enter the following code:

//--------------------------------------------------------------------------------
//! This class defines merely a parameter container for
//! visualization attributes and a shape enumeration.
/*!
// \file    BaseMessenger.h
// \author  JDoe
// \date    2016-03-03
*/
//--------------------------------------------------------------------------------

#pragma once

#include <mlModuleIncludes.h>
#include <mlBase.h>
#include <mlLinearAlgebra.h>


// Local includes
#include "MLBaseCommunicationSystem.h"


ML_START_NAMESPACE

//--------------------------------------------------------------------------------

//! This enumeration lists all possible
//! shape types used in the owner and receiver modules.
enum MessengerShapeType 
{
  ShapeTypeCube = 0,
  ShapeTypeSphere = 1
};

//--------------------------------------------------------------------------------

//! This class defines merely a parameter container for
//! visualization attributes and a shape enumeration.
class MLBASECOMMUNICATION_EXPORT BaseMessenger : public Base
{
public:

  //! Constructor.
  BaseMessenger();

  //! Copy constructor.
  BaseMessenger(const BaseMessenger& baseMessenger);

  //! Standard destructor.
  virtual ~BaseMessenger();

  //! \name Methods to retrieve attributes.
  //@{
  inline const Vector3&     getPosition()  const { return _position; }
  inline const Vector3&     getColor()     const { return _color; }
  inline MLdouble           getDiameter()  const { return _diameter; }
  inline MessengerShapeType getShapeType() const { return _shapeType; }
  //@}

  //! \name Methods to set attributes.
  //@{
  inline void setPosition(const Vector3& newPosition)  { _position = newPosition; }
  inline void setColor(const Vector3& newColor)        { _color = newColor; }
  inline void setDiameter(MLdouble newDiameter)        { _diameter = newDiameter; }
  inline void setShapeType(MessengerShapeType newType) { _shapeType = newType; }
  //@}

private:

  //! \name Member variables.
  //@{
  Vector3 _position;
  Vector3 _color;
  MLdouble _diameter;
  MessengerShapeType _shapeType;
  //@}

  //! Implements interface for the runtime type system of the ML.
  ML_CLASS_HEADER(BaseMessenger)
};

//--------------------------------------------------------------------------------

ML_END_NAMESPACE

15.2.5. Add Contents to BaseMessenger.cpp

Open BaseMessenger.cpp and enter the following code:

//--------------------------------------------------------------------------------
//!
/*!
 // \file    BaseMessenger.cpp
 // \author  JDoe
 // \date    2016-03-03
 */
//--------------------------------------------------------------------------------

// Local includes
#include "BaseMessenger.h"

ML_START_NAMESPACE


//! Implements code for the runtime type system of the ML
ML_MODULE_CLASS_SOURCE(BaseMessenger, Base);

//--------------------------------------------------------------------------------

BaseMessenger::BaseMessenger() : Base()
{
  // Set default values.

  _position.assign(0.0, 0.0, 0.0);
  _color.assign(1.0, 0.0, 0.0); // red
  _diameter = 1.0;
}

//--------------------------------------------------------------------------------

BaseMessenger::BaseMessenger(const BaseMessenger& baseMessenger) : Base()
{
  // Just copy values of the given object.

  _position = baseMessenger.getPosition();
  _color    = baseMessenger.getColor();
  _diameter = baseMessenger.getDiameter();
}

//--------------------------------------------------------------------------------

BaseMessenger::~BaseMessenger()
{
  // Not needed.
}

//--------------------------------------------------------------------------------


ML_END_NAMESPACE

15.2.6. Editing MLBaseCommunicationInit.cpp

Add the initialization of the BaseMessenger class (runtime type system).

  1. Open MLBaseCommunicationInit.cpp.

  2. Add the include of BaseMessenger.h. Result:

    // Include all module headers
    #include "mlBaseOwner.h"
    #include "BaseMessenger.h"
  3. Add the initialization of BaseMessenger.h. Result:

    int MLBaseCommunicationInit ()
    {
      // Add initClass calls from all other modules here...
      BaseOwner::initClass();
      BaseMessenger::initClass();
    
      return 1;
    } 

At this point, the project should be compilable.

15.2.7. Editing mlBaseOwner.h

Open mlBaseOwner.h.

  1. Add the include of BaseMessenger.h. Result:

    // Local includes
    #include "MLBaseCommunicationSystem.h"
    #include "BaseMessenger.h" 
  2. Add a destructor to the class:

      //! Constructor.
      BaseOwner();
    
      //! Destructor.
      ~BaseOwner();
  3. Add a private member variable of type BaseMessenger pointer since this module is the owner of the Base object. Result:

    private:
    
      //! \name Member variables.
      //@{
      BaseMessenger* _baseMessenger;
      //@}
  4. Add a private method to set the value in the Messenger object to the module's field values:

      // Implements interface for the runtime type system of the ML.
      ML_MODULE_CLASS_HEADER(BaseOwner)
    
      //! Set the field values to the output messenger.
      void _setFieldValuesToMessenger();

15.2.8. Editing mlBaseOwner.cpp

15.2.8.1. Adding the construction of a new BaseMessenger Object

Open mlBaseOwner.cpp and add the construction of a new BaseMessenger object and its parameterization to the constructor of the BaseOwner module. Use setBaseValueAndAddAllowed to ensure that the base type will be checked when drawing connetions in the user interface.

Also, add the destruction of the _baseMessenger object to the destructor.

Result:

BaseOwner::BaseOwner () : Module(0, 0)
{
  // Suppress calls of handleNotification on field changes to
  // avoid side effects during initialization phase.
  handleNotificationOff();

  // Allocate memory for the BaseMessenger object.
  // Delete the object in this module's destructor.
  ML_CHECK_NEW(_baseMessenger, BaseMessenger());

  // Add fields for the interface.
  // Set the pointer to the BaseMessenger object to the output field.
  _outputMessengerFld = addBase("outputMessenger");
  
  _outputMessengerFld->setBaseValueAndAddAllowedType(_baseMessenger);;

  static const char * const shapeTypeValues[] = { "Cube", "Sphere" };
  _shapeTypeFld = addEnum("shapeType", shapeTypeValues, 2);
  _shapeTypeFld->setEnumValue(0);
  _translationFld = addVector3("translation");
  _translationFld->setVector3Value(Vector3());
  _colorFld = addColor("color");
  _colorFld->setColorValue(1,1,1);
  _diameterFld = addDouble("diameter");
  _diameterFld->setDoubleValue(1);

  _setFieldValuesToMessenger();

  // Reactivate calls of handleNotification on field changes.
  handleNotificationOn();
}

// ...

BaseOwner::~BaseOwner()
{
  ML_DELETE(_baseMessenger);
}

15.2.8.2. Editing handleNotification

Change handleNotification so that it touches the output Base field after setting the module's field values to the BaseMessenger object. Result:

void BaseOwner::handleNotification(Field* field)
{
  // Handle changes of module parameters and input image fields here.
  bool touchOutputs = false;

  if ((field == _shapeTypeFld)   ||
      (field == _translationFld) ||
      (field == _colorFld)       ||
      (field == _diameterFld))
  {
    touchOutputs = true;
  }

  if (touchOutputs) 
  {
    // Set the current parameter values to the messenger object
    // and touch the output field so the receiver generates its
    // scene anew.

    _setFieldValuesToMessenger();

    _outputMessengerFld->touch();
  }
}

15.2.8.3. Editing activateAttachments

After a saved network has been loaded and all the modules and their connection have been regenerated, the activateAttachements methods are called. We use this to regenerate the Base object with the saved parameters of the BaseOwner module.

Result:

void BaseOwner::activateAttachments()
{  
  // Update members to new field state here.
  _setFieldValuesToMessenger();
  _outputMessengerFld->touch();

  // Call super class functionality to enable notification handling again.
  Module::activateAttachments();
}

15.2.8.4. Implementing Setting the Parameters in BaseMessenger

Implement the setting of the parameters in the BaseMessenger according to the module's fields after the module has been loaded in a network (with restored field values) in activateAttachments(). Result:

void BaseOwner::activateAttachments ()
{
  // Update members to new field state here.
 _setFieldValuesToMessenger();

  // Call super class functionality to enable notification handling again.
  Module::activateAttachments ();
}

15.2.8.5. Implementing the method _setFieldValuesToMessenger()

void BaseOwner::_setFieldValuesToMessenger()
{
  _baseMessenger->setPosition(_translationFld->getVector3Value());
  _baseMessenger->setColor(_colorFld->getVector3Value());
  _baseMessenger->setDiameter(_diameterFld->getDoubleValue());
  _baseMessenger->setShapeType(
    static_cast<MessengerShapeType>(
      _shapeTypeFld->getEnumValue()
    )
  );
}

ML_END_NAMESPACE

Save the file mlBaseOwner.cpp.

15.2.9. Making MLBaseCommunication classes known

Make the classes of the project MLBaseCommunication (and with it, the BaseMessenger) known to other projects:

  1. Open the file CMakeLists.txt of the project in a text editor.

  2. Change the last lines of the file, from:

    mlab_install(MLBaseCommunication NS MeVisLab)

    to

    mlab_install(MLBaseCommunication NS MeVisLab EXPORT)
    mlab_install_headers(MLBaseCommunication)
          

Compile the project and restart MeVisLab. To check the final module, enter BaseOwner in the quick search and add it.

Figure 15.9. Resulting BaseOwner Module

Resulting BaseOwner Module

[Tip]Tip

This example is delivered with MeVisLab (.def file in $(InstallDir)Packages/MeVisLab/Examples/Modules/GettingStarted/MLBaseCommunicationExample, source files in $(InstallDir)Packages/MeVisLab/Examples/Sources/GettingStarted/MLBaseCommunicationExample). The module can be added via quick search.

15.2.10. Adding an object wrapper for MLBaseCommunication objects

To use the MLBaseCommunication in scripting, an object wrapper can be implemented. How this is done is explained here.