Migration guide from CamiTK 4.1 to CamiTK 5.0

The complete guide to migrate your source code from CamiTK 4.1 to CamiTK 5.0

CamiTK 5 fixes a lot of bugs (see the Release Notes) and introduces one big new feature: the viewer extension (ability to define plugins that provides new viewers, see the viewer extension documentation for more information). Viewer extensions are now a first class type of extension, and are as easy to create and integrate as action and component extensions. They also makes applications easier to build and test.

The methods in the Component class that used a pointer to a Viewer in the previous versions of CamiTK are now using a QString to name the required viewer. This ensures safety and modularity to access a specific viewer (e.g., to manage the case where the corresponding viewer extension is not currently loaded).

Deprecated API members

Since CamiTK 5.0

Thanks to the CAMITK_API_DEPRECATED macro, deprecated methods are now marked by the compiler. You should get a compilation warnings with a explanation message if you use deprecated CamiTK API members.

This is the list of deprecated members since CamiTK 4.1 (they still works but beware: they might disappear very soon from the API):

  • setVisibility(Viewer*, bool): Please use setVisibility(QString viewerName, bool) instead
  • getVisibility(Viewer*) const: Please use getVisibility(QString viewerName, bool) instead
  • setFrameVisibility(Viewer*, bool) (in Component, Frame or InterfaceFrame): Please use setFrameVisibility(QString viewerName, bool) instead
  • getFrameVisibility(Viewer*) (in Component, Frame or InterfaceFrame): Please use Component::getFrameVisibility(QString viewerName, bool) instead

As the viewers are now either set in the central part of the main window or in a dock, the following methods are also deprecated in MainWindow:

  • showViewer(Viewer*, bool): This method is now called showDockViewer(..)
  • removeViewer(Viewer* viewer): Using the new viewer extension system, removing viewers is not required anymore (just hide its widget). Removing viewer can be harmful and result in crash. The viewer’s GUI can now simply be hidden, therefore there is no need to remove a viewer.

Also now refreshInterfaceNode() is not required anymore. Everything should now be done transparently. If it still does not work in your case you can call setNodeModified(true) to force a specific InterfaceNode/Component to refresh the visualisation in the explorer.

Finally, in previous CamiTK versions, viewers were mostly created using the singleton pattern. An instance of a viewer was accessible through a static method of the viewer’s classe (e.g., InteractiveViewer::get3DViewer() or MedicalImageViewer::getInstance()). Thanks to the viewer extension mechanism, this system is more open to change and more securely managed by the Application class. A viewer extension is in charge or instantiating one or more viewers and registered them using specific name(s) when it is loaded in the application. Once the viewer extension is loaded, the viewers are now accessible through the Application::getViewer() and Application::getViewers() methods. You may also easily instantiate a new viewer of any loaded viewer extension for your private use (e.g., in a specific action or main window GUI), by calling Application::getNewViewer(QString name, QString className). Application can now be considered as a viewer factory. In order to use a viewer in an extension, you now need to declare the dependency in the CMakeLists.txt using the NEEDS_VIEWER_EXTENSION parameter (see the troubleshooting section below)

Viewer management migration step-by-step

Since CamiTK 5.0

As viewers are now first-class extension in CamiTK, all the viewers previously available directly in CamiTK core library are now available as independent extensions. This necessitates some minor change in order to migrate your code. This section will guide you through these changes.

The following viewer extensions are provided by CamiTK Community Edition since CamiTK 5.0:

Extension Viewer class Header Provided viewer instance name(s)1
explorer Explorer Explorer.h “Explorer”
frameexplorer FrameExplorer FrameExplorer.h “Frame Explorer”
propertyexplorer PropertyExplorer PropertyExplorer.h “Property Explorer”
interactivegeometryviewer InteractiveGeometryViewer InteractiveGeometryViewer.h “3D Viewer”2
interactivesliceviewer InteractiveSliceViewer InteractiveSliceViewer.h “Axial Viewer”2, “Coronal Viewer”2, “Sagittal Viewer”2 and “Arbitrary Viewer”2
medicalimageviewer MedicalImageViewer MedicalImageViewer.h “Medical Image Viewer”
bitmap Bitmap Bitmap.h “Bitmap Viewer”
actionviewer3 ActionViewer ActionViewer.h “Action Viewer”

1 You are free to instance your own specific viewer from the provieded viewer class. The extension comes with default instances that are automatically available once the extension is loaded. 2 These names were kept for facilitating the migration to CamiTK 5.0 3 This viewer manages actions (and not components as all the other viewers)

Note that the tutorials CEP provides two additionnal examples:

  • MixedViewer (shows how to reuse existing viewer)
  • TextViewer (shows how to create a specific viewer from scratch).

See the tutorial list for more information.

The following subsections shows how to migrate your code through the use of examples. One of the main consequence of relaxing the dependency to viewers is that they are now access by their names.

Component visibility

# Before CamiTK 5.0
comp->getVisibility(InteractiveViewer::get3DViewer())

# Since CamiTK 5.0
comp->getVisibility("3D Viewer");

Component frame visibility

# Before CamiTK 5.0
currentComponent->setFrameVisibility(InteractiveViewer::get3DViewer(), false);

# Since CamiTK 5.0
currentComponent->setFrameVisibility("3D Viewer", false);

generic viewer use case

# Before CamiTK 5.0
InteractiveViewer::getAxialViewer()->...

# Since CamiTK 5.0
Application::getViewer("Axial Viewer")
// or more secure: test if the extension was correctly loaded first by checking the get values before using it:
AxialViewer* axialViewer = dynamic_cast<AxialViewer*>(Application::getViewer("Axial Viewer"));
if (axialViewer != nullptr) {
  axialViewer->...
}

viewer specific use case 1

# Before CamiTK 5.0
InteractiveViewer::get3DViewer()->refresh();

# Since CamiTK 5.0
Application::refresh();
# or (although this is not recommended as there is no benefit to call `refresh()` on a specific viewer,
# even for performance reason, and as this might introduce discrepancy in visual feedback)
Application::getViewer("3D Viewer")->refresh();

viewer specific use case 2

# Before CamiTK 5.0
InteractiveViewer::get3DViewer()->getRendererWidget()->removeProp(oldProp);

# Since CamiTK 5.0
InteractiveGeometryViewer* default3DViewer = dynamic_cast<InteractiveGeometryViewer*>(Application::getViewer("3D Viewer"))
if (default3DViewer != nullptr) {
  default3DViewer->getRendererWidget()->removeProp(oldVolume);
)

viewer specific use case 3

# Before CamiTK 5.0
QObject::connect(InteractiveViewer::get3DViewer(), SIGNAL(selectionChanged()), this, SLOT(apply()));

# Since CamiTK 5.0
QObject::connect(Application::getViewer("3D Viewer"), SIGNAL(selectionChanged()), this, SLOT(apply()));

viewer specific use case 4

# Before CamiTK 5.0
MedicalImageViewer::getInstance()->setVisibleViewer(MedicalImageViewer::VIEWER_CORONAL);

# Since CamiTK 5.0
MedicalImageViewer* medicalImageViewer = dynamic_cast<MedicalImageViewer*>(Application::getViewer("Medical Image Viewer"));
if (medicalImageViewer != nullptr) {
    medicalImageViewer->setVisibleViewer(MedicalImageViewer::VIEWER_CORONAL);
}

viewer specific use case 5

# Before CamiTK 5.0
myNewViewer = InteractiveViewer::getNewViewer(QString("My New Viewer"), InteractiveViewer::SLICE_VIEWER);

# Since CamiTK 5.0,
// Viewer type is specified by its name (see table above)
myNewViewer = Application::getNewViewer("My New Viewer", "InteractiveSliceViewer"); 

Migration troubleshooting

Component not updated in the explorer

My action is instantiating a new component on the fly, but it does not appear in the explorer

As viewers are not directly linked with CamiTK Core anymore, you now need to call Application::refresh() after instantiating a new Component (or a subclass) on the fly (e.g., if there is an instantiation of a new ImageComponent during an image acquisition, call Application::refresh() just after the instanciation).

viewer’s include file not found

If you get an error about a viewer include not found. E.g.:

fatal error: MedicalImageViewer.h: No such file or directory
  xxx | #include <MedicalImageViewer.h>

You just have to add the following option in the camitk_extension macro to specify that your extension requires a specific viewer extension (this is the same mechanism as if your extension depends on an action or component extension):

camitk_extension(
                 ...
                 NEEDS_VIEWER_EXTENSION medicalimageviewer
)

getInstance() not found

If you get an error about the getInstance() method of viewer class not found. E.g.:

error: getInstance is not a member of MedicalImageViewer
  xxx |     MedicalImageViewer::getInstance()->setVisibleViewer(MedicalImageViewer::VIEWER_CORONAL)

You just have to replace the corresponding line by:

    Application::getViewer("Medical Image Viewer")->setVisibleViewer(MedicalImageViewer::VIEWER_CORONAL);

As the viewers are now provided through extensions, we strongly advise you to check the return value of Application::getViewer("Medical Image Viewer") first. So a complete safer replacement could be:

    MedicalImageViewer* medicalImageViewer = dynamic_cast<MedicalImageViewer*>(Application::getViewer("Medical Image Viewer"));
    if (medicalImageViewer != nullptr) {
        medicalImageViewer->setVisibleViewer(MedicalImageViewer::VIEWER_CORONAL);
    }
    else {
      CAMITK_WARNING("Medical Image Viewer required.")
    }    

get3DViewer (or getAxialViewer, getCoronalViewer, or getSagittalViewer) is not a member of camitk::InteractiveViewer

As all the previously available viewers are now extensions, the getters of InteractiveViewer default instances are also available through their corresponding extensions.

So if you get the following error (or similar):

error: get3DViewer is not a member of camitk::InteractiveViewer
  xxx |             InteractiveViewer::get3DViewer()->...; // ... corresponds a specific action to perform (i.e., a call to one of the viewer's specific method)

Just replace the affected line with

    InteractiveViewer* viewer3D = dynamic_cast<InteractiveViewer*>(Application::getViewer("3D Viewer"));
    if (viewer3D != nullptr) {
        viewer3D->...;  // replace ... by the expression that you were using in your code
    }
    else {
      CAMITK_WARNING("3D Interactive Viewer extension not available but required")
    }

The following table gives the CamiTK 5 corresponding calls for default InteractiveViewer instances:

In CamiTK 4 In CamiTK 5
InteractiveViewer::get3DViewer() Application::getViewer("3D Viewer")
InteractiveViewer::getAxialViewer() Application::getViewer("Axial Viewer")
InteractiveViewer::getCoronalViewer() Application::getViewer("Coronal Viewer")
InteractiveViewer::getSagittalViewer() Application::getViewer("Sagittal Viewer")

You will also probably need to add the dependency to the viewer using NEEDS_VIEWER_EXTENSION as explained above.

As viewers are now extensions, it means that in some (rare) cases the viewer extension might not have been loaded. In this case any extension that uses the viewer should not have been loaded either. In some extremely rare cases (that are theoretical and have not yet occured out in pratical life), the call to Application::getViewer will result in a crash. To avoid this unexpected potential problem and avoid static analysis tool warnings, it is better to check the result of Application::getViewer against nullptr.