Skip to content

Commit

Permalink
2023 Week 48 (Very Late November)
Browse files Browse the repository at this point in the history
The following - possibly updated - changelog can be viewed as formatted
article at https://phabricator.mitk.org/w/mitk/changelog/2023.48/.

= 🛠 Third-party dependency changes =

//none//

= ✨ New features =

- Build system
  - MITK is now compatible with the latest version 17.8 of Visual Studio 2022

= 🐛 Bugfixes =

- Segmentation
  - Fixed several crashes in some edge cases of segmentation group handling
  - Made message boxes for segmentation group deletion more clear and explicit
  - Segment Anything segmentation tool now works for all view directions of a 2-d images
- Point set interaction
  - Fixed selection glitch of points in close proximity to other points
- Image statistics
  - Fixed potential crash with line profiles based on open planar figures exceeding the bounds of an image
- DICOM inspector
  - Fixed initial data selection glitch

= 🔥 API-breaking changes =

== Properties ==

IMPORTANT: Make sure to also read the mitk::PropertyList deserialization section below!

We added two pure virtual methods to the [[https://docs.mitk.org/nightly/classmitk_1_1BaseProperty.html | mitk::BaseProperty]] class for JSON serialization, that must be implemented in all derived classes:

- `bool ToJSON(nlohmann::json& j) const`
- `bool FromJSON(const nlohmann::json& j)`

See the [[https://docs.mitk.org/nightly/classmitk_1_1BaseProperty.html | documentation]] of these methods for important implementation hints.

You find plenty of example impementations in our code base for all existing property classes. Here is the implementation for `mitk::StringProperty`:

```lang=cpp, name="JSON (de)serialization of mitk::StringProperty"
bool mitk::StringProperty::ToJSON(nlohmann::json& j) const
{
  j = this->GetValueAsString();
  return true;
}

bool mitk::StringProperty::FromJSON(const nlohmann::json& j)
{
  this->SetValue(j.get<std::string>());
  return true;
}
```

If you have more complex types (that can be default-constructed) you certainly want to utilize the [[https://json.nlohmann.me/features/arbitrary_types/ | arbitrary type conversions]] feature of the JSON for Modern C++ library by implementing all the (de)serialization logic in `void to_json(nlohmann::json& j, const <my_type>&)` and `void from_json(const nlohmann::json& j, <my_type>&)` functions, located in the same namespace as your type.
This reduces the implementation of the `mitk::BaseProperty` methods mentioned above to the same two-liners as shown in the example for `mitk::StringProperty`. Here is the implementation for `itk::RGBPixel<>`, which is used as value type in `mitk::ColorProperty` (`typedef itk::RGBPixel<float> Color`):

```lang=cpp, name="itk::RGBPixel<> type conversion for JSON (de)serialization"
namespace itk
{
  template <typename TComponent>
  void to_json(nlohmann::json& j, const RGBPixel<TComponent>& c)
  {
    j = nlohmann::json::array();

    for (size_t i = 0; i < 3; ++i)
      j.push_back(c[i]);
  }

  template <typename TComponent>
  void from_json(const nlohmann::json& j, RGBPixel<TComponent>& c)
  {
    for (size_t i = 0; i < 3; ++i)
      j.at(i).get_to(c[i]);
  }
}
```

Now, the JSON (de)serialization implementation of `mitk::ColorProperty` boils down to:

```lang=cpp, name="JSON (de)serialization of mitk::ColorProperty"
bool mitk::ColorProperty::ToJSON(nlohmann::json& j) const
{
  j = this->GetColor();
  return true;
}

bool mitk::ColorProperty::FromJSON(const nlohmann::json& j)
{
  this->SetColor(j.get<Color>());
  return true;
}
```

NOTE: If (de)serialization of your property class does not make any sense, i. e. for cases like `mitk::SmartPointerProperty`, simply return `false` from these methods.

== mitk::PropertyList deserialization ==

Property lists can now be (de)serialized in JSON format as well.
However, the deserialization needs to know upfront which property type is tied to the type names found in the JSON representation.

In addition to the `To/FromJSON()` method implementations mentioned above, you also need to register your property type for that purpose with the [[https://docs.mitk.org/nightly/classmitk_1_1IPropertyDeserialization.html | mitk::IPropertyDeserialization]] core service:

```lang=cpp, name="Register property types for the JSON deserialization of mitk::PropertyList"

mitk::CoreServicePointer<mitk::IPropertyDeserialization> service(mitk::CoreServices::GetPropertyDeserialization());

service->RegisterProperty<MyPropertyType>();
```

NOTE: The perfect location for this registration code is the `Load()` method of your module activator (see [[https://docs.mitk.org/nightly/classMitkCoreActivator.html | MitkCoreActivator]] for an example).
  • Loading branch information
kislinsk committed Nov 30, 2023
2 parents e5401e9 + 22173bf commit 4ae412d
Show file tree
Hide file tree
Showing 147 changed files with 4,004 additions and 688 deletions.
2 changes: 2 additions & 0 deletions CMake/BuildConfigurations/WorkbenchRelease.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ set(MITK_CONFIG_PLUGINS ${MITK_CONFIG_PLUGINS}
org.mitk.gui.qt.matchpoint.visualizer
org.mitk.gui.qt.matchpoint.evaluator
org.mitk.gui.qt.matchpoint.manipulator
org.mitk.gui.qt.dicominspector
)

if(NOT MITK_USE_SUPERBUILD)
set(BUILD_CoreCmdApps ON CACHE BOOL "" FORCE)
set(BUILD_MatchPointCmdApps ON CACHE BOOL "" FORCE)
set(BUILD_SegmentationCmdApps ON CACHE BOOL "" FORCE)
set(BUILD_DICOMCmdApps ON CACHE BOOL "" FORCE)
endif()

set(MITK_VTK_DEBUG_LEAKS OFF CACHE BOOL "Enable VTK Debug Leaks" FORCE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The following items describe some issues about MITK on a more abstract level.
-# \subpage ReaderWriterPage
-# \subpage MitkImagePage
-# \subpage MITKSegmentationTaskListsPage
-# \subpage MITKROIPage
-# \subpage PropertiesPage
-# \subpage GeometryOverviewPage
-# \subpage PipelineingConceptPage
Expand Down
154 changes: 154 additions & 0 deletions Documentation/Doxygen/3-DeveloperManual/Concepts/MITKROIs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# MITK ROI {#MITKROIPage}

[TOC]

## Disclaimer

Until the MITK ROI file format is going to be officially announced in a 2024 release of MITK, the file format must be considered experimental and is prone to change without any prior warning.

## Overview

MITK ROI is a JSON-based file format defining a collection of region of interests (ROIs).

ROIs must have an ID (unsigned integer) and their shape is currently considered to be an axis-aligned bounding box.
Its bounds are defined by minimum and maximum index coordinates, typically relative to an image.
Custom properties of various known types can be optionally attached to a ROI.
A few of these properties are used by MITK to define the appearance of a rendered ROI, for example:

- "color" (mitk::ColorProperty): Color/RGB triplet of the rendered ROI (default: white \[1.0, 1.0, 1.0\])
- "opacity" (mitk::FloatProperty): Opacity of the rendered ROI (default: 100% \[1.0\])
- "lineWidth" (mitk::FloatProperty): Line width of the egdes of the rendered ROI (default: 1px \[1.0\])

ROIs can be optionally time-resolved and define both coordinates and properties per time step, allowing for a dynamic appearance, position, and size over time.

ROIs also display a caption at their bottom-left corner (supporting multiple lines), that can be set once per MITK ROI file for all contained ROIs.
Placeholders enclosed by braces in the caption are substituted by their corresponding ROI property values at runtime.
The default caption is "{name} ({ID})", where ``{ID}`` is a special placeholder for the ID of a ROI (technically not a ROI property), and ``{name}`` refers to the ROI property "name" (typically an mitk::StringProperty).

Last but not least the reference (image) geometry of the ROIs in an MITK ROI file must be specified to be able to map all index coordinates to actual world coordinates.
A geometry is defined by an origin, the pixel/voxel spacing, a size, and optionally the number of time steps in case of a time-resolved MITK ROI file.

## File format

As all features are explained in the overview above, the JSON-based file format is defined here by two examples with minimal additional notes: one example for a static MITK ROI file and one example for a time-resolved MITK ROI file.

### Static MITK ROI file

This example contains two ROIs for detected tumors in an image with certain confidence.
Names and confidence values will be displayed in separate lines for each ROI.

~~~{.json}
{
"FileFormat": "MITK ROI",
"Version": 1,
"Name": "Static example",
"Caption": "{name}\nConfidence: {confidence}",
"Geometry": {
"Origin": [0, 0, 0],
"Spacing": [1, 1, 3],
"Size": [256, 256, 49]
},
"ROIs": [
{
"ID": 0,
"Min": [4, 4, 1],
"Max": [124, 124, 31],
"Properties": {
"StringProperty": {
"name": "tumor",
"comment": "Detected a tumor with 95% confidence.",
"note": "Properties are grouped by their type to reduce verbosity."
},
"ColorProperty": {
"color": [0, 1, 0]
},
"FloatProperty": {
"confidence": 0.95
}
}
},
{
"ID": 1,
"Min": [132, 4, 1],
"Max": [252, 60, 15],
"Properties": {
"StringProperty": {
"name": "Another tumor",
"comment": "Maybe another tumor (confidence only 25%)."
},
"ColorProperty": {
"color": [1, 0, 0]
},
"FloatProperty": {
"confidence": 0.25
}
}
}
]
}
~~~

Further hints:

- "FileFormat" ("MITK ROI"), "Version" (1), and "Geometry" are mandatory.
- "Name" is optional. If not set, the file name is used by MITK instead.
- ROIs are defined by JSON objects in the "ROIs" JSON array.
- See the derived classes of mitk::BaseProperty for an overview of known property types.

### Time-resolved MITK ROI file

This example only contains a single ROI but it is defined for several time steps.
Fallbacks of time step properties to default properties are demonstrated as well.

~~~{.json}
{
"FileFormat": "MITK ROI",
"Version": 1,
"Name": "Time-resolved example",
"Geometry": {
"Origin": [0, 0, 0],
"Spacing": [1, 1, 3],
"Size": [256, 256, 49],
"TimeSteps": 3
},
"ROIs": [
{
"ID": 0,
"Properties": {
"ColorProperty": {
"color": [1, 0, 0]
},
"StringProperty": {
"name": "Color-changing ROI"
}
},
"TimeSteps": [
{
"t": 0,
"Min": [4, 4, 1],
"Max": [124, 124, 31]
},
{
"t": 2,
"Min": [14, 14, 11],
"Max": [121, 121, 28],
"Properties": {
"ColorProperty": {
"color": [0, 1, 0]
}
}
}
]
}
]
}
~~~

Further hints:

- The geometry defines 3 time steps.
- The "Properties" directly in the ROI function as fallbacks, if they are not defined for a certain time step.
- Time time step indices "t" are mandatory. The ROI is only present at time steps 0 and 2.
- The ROI is red (fallback) at time step 0 and green at time step 2.
- Its extents are larger at time step 0 than at time step 2.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ Optional properties of a segmentation task include a task name and description a
The complete set of properties is specified further below in the file format specification.

MITK Segmentation Task Lists must be considered experimental at the moment and are prone to change without any prior warning.
They are currently supported only in the MITK FlowBench application where a dedicated widget for task navigation and management appears if an MITK Segmentation Task List has been opened.

## File format

Expand Down
3 changes: 3 additions & 0 deletions Modules/AppUtil/src/mitkBaseApplication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ found in the LICENSE file.

#include <usModuleSettings.h>

#include <vtkLogger.h>
#include <vtkOpenGLRenderWindow.h>
#include <QVTKOpenGLNativeWidget.h>

Expand Down Expand Up @@ -663,6 +664,8 @@ namespace mitk
{
if (nullptr == qApp)
{
vtkLogger::SetStderrVerbosity(vtkLogger::VERBOSITY_WARNING);

vtkOpenGLRenderWindow::SetGlobalMaximumNumberOfMultiSamples(0);

auto defaultFormat = QVTKOpenGLNativeWidget::defaultFormat();
Expand Down
4 changes: 2 additions & 2 deletions Modules/BoundingShape/include/mitkBoundingShapeVtkMapper2D.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ found in the LICENSE file.
#include <vtkPolyDataMapper2D.h>
#include <vtkPropAssembly.h>
#include <vtkSmartPointer.h>
#include <vtkSphereSource.h>
#include <vtkCubeSource.h>

namespace mitk
{
Expand All @@ -46,7 +46,7 @@ namespace mitk
vtkSmartPointer<vtkCutter> m_Cutter;
vtkSmartPointer<vtkPlane> m_CuttingPlane;
unsigned int m_LastSliceNumber;
std::vector<vtkSmartPointer<vtkSphereSource>> m_Handles;
std::vector<vtkSmartPointer<vtkCubeSource>> m_Handles;
vtkSmartPointer<vtkPropAssembly> m_PropAssembly;
double m_ZoomFactor;

Expand Down
5 changes: 0 additions & 5 deletions Modules/BoundingShape/include/mitkBoundingShapeVtkMapper3D.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,8 @@ found in the LICENSE file.
#define mitkBoundingShapeVtkMapper3D_h

#include <MitkBoundingShapeExports.h>

#include <mitkVtkMapper.h>

#include <vtkPropAssembly.h>
#include <vtkSmartPointer.h>
#include <vtkSphereSource.h>

namespace mitk
{
class MITKBOUNDINGSHAPE_EXPORT BoundingShapeVtkMapper3D : public VtkMapper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ void mitk::BoundingShapeInteractor::DataNodeChanged()
newInputNode->AddProperty(selectedColorPropertyName, mitk::ColorProperty::New(0.0, 1.0, 0.0));

if (deselectedColor.IsNull())
newInputNode->AddProperty(deselectedColorPropertyName, mitk::ColorProperty::New(1.0, 1.0, 1.0));
newInputNode->AddProperty(deselectedColorPropertyName, mitk::ColorProperty::New(1.0, 0.0, 0.0));

newInputNode->SetProperty(boundingShapePropertyName, mitk::BoolProperty::New(true));
newInputNode->AddProperty(activeHandleIdPropertyName, mitk::IntProperty::New(-1));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,15 @@ found in the LICENSE file.
#include <vtkActor2D.h>
#include <vtkAppendPolyData.h>
#include <vtkCoordinate.h>
#include <vtkCubeSource.h>
#include <vtkMath.h>
#include <vtkPointData.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper2D.h>
#include <vtkProperty2D.h>
#include <vtkSphereSource.h>
#include <vtkStripper.h>
#include <vtkTransformFilter.h>
#include <vtkTransformPolyDataFilter.h>

static vtkSmartPointer<vtkSphereSource> CreateHandle()
{
auto handle = vtkSmartPointer<vtkSphereSource>::New();

handle->SetPhiResolution(8);
handle->SetThetaResolution(16);

return handle;
}

namespace mitk
{
class BoundingShapeVtkMapper2D::Impl
Expand Down Expand Up @@ -71,7 +59,6 @@ mitk::BoundingShapeVtkMapper2D::LocalStorage::LocalStorage()
m_ZoomFactor(1.0)
{
m_Actor->SetMapper(m_Mapper);
m_Actor->GetProperty()->SetOpacity(0.3);
m_Actor->VisibilityOn();

m_HandleActor->SetMapper(m_HandleMapper);
Expand All @@ -89,7 +76,7 @@ mitk::BoundingShapeVtkMapper2D::LocalStorage::LocalStorage()
m_Cutter->SetCutFunction(m_CuttingPlane);

for (int i = 0; i < 6; ++i)
m_Handles.push_back(CreateHandle());
m_Handles.push_back(vtkSmartPointer<vtkCubeSource>::New());

m_PropAssembly->AddPart(m_Actor);
m_PropAssembly->AddPart(m_HandleActor);
Expand Down Expand Up @@ -139,6 +126,7 @@ void mitk::BoundingShapeVtkMapper2D::Update(mitk::BaseRenderer *renderer)
void mitk::BoundingShapeVtkMapper2D::SetDefaultProperties(DataNode *node, BaseRenderer *renderer, bool overwrite)
{
Superclass::SetDefaultProperties(node, renderer, overwrite);
node->AddProperty("opacity", FloatProperty::New(0.2f), renderer, overwrite);
}

mitk::BoundingShapeVtkMapper2D::BoundingShapeVtkMapper2D() : m_Impl(new Impl)
Expand Down Expand Up @@ -340,7 +328,7 @@ void mitk::BoundingShapeVtkMapper2D::GenerateDataForRenderer(BaseRenderer *rende
if (handleSizeProperty != nullptr)
initialHandleSize = handleSizeProperty->GetValue();
else
initialHandleSize = 1.0 / 40.0;
initialHandleSize = 0.02;

mitk::Point2D displaySize = renderer->GetDisplaySizeInMM();
double handleSize = ((displaySize[0] + displaySize[1]) / 2.0) * initialHandleSize;
Expand All @@ -365,7 +353,9 @@ void mitk::BoundingShapeVtkMapper2D::GenerateDataForRenderer(BaseRenderer *rende
{
Point3D handleCenter = m_Impl->HandlePropertyList[handleIdx].GetPosition();

handle->SetRadius(handleSize);
handle->SetXLength(handleSize);
handle->SetYLength(handleSize);
handle->SetZLength(handleSize);
handle->SetCenter(handleCenter[0], handleCenter[1], handleCenter[2]);

// show handles only if the corresponding face is aligned to the render window
Expand Down Expand Up @@ -416,12 +406,8 @@ void mitk::BoundingShapeVtkMapper2D::GenerateDataForRenderer(BaseRenderer *rende
cutPolyData->SetPolys(stripper->GetOutput()->GetLines());

localStorage->m_Actor->GetMapper()->SetInputDataObject(cutPolyData);
mitk::ColorProperty::Pointer selectedColor = dynamic_cast<mitk::ColorProperty *>(node->GetProperty("color"));
if (selectedColor != nullptr)
{
mitk::Color color = selectedColor->GetColor();
localStorage->m_Actor->GetProperty()->SetColor(color[0], color[1], color[2]);
}

this->ApplyColorAndOpacityProperties(renderer, localStorage->m_Actor);

if (activeHandleId != nullptr)
{
Expand Down Expand Up @@ -462,6 +448,15 @@ vtkProp *mitk::BoundingShapeVtkMapper2D::GetVtkProp(BaseRenderer *renderer)
return m_Impl->LocalStorageHandler.GetLocalStorage(renderer)->m_PropAssembly;
}

void mitk::BoundingShapeVtkMapper2D::ApplyColorAndOpacityProperties(BaseRenderer *, vtkActor *)
void mitk::BoundingShapeVtkMapper2D::ApplyColorAndOpacityProperties(BaseRenderer *renderer, vtkActor *actor)
{
auto* property = actor->GetProperty();

std::array<float, 3> color = { 1.0, 0.0, 0.0 };
this->GetDataNode()->GetColor(color.data(), renderer);
property->SetColor(color[0], color[1], color[2]);

float opacity = 0.2f;
this->GetDataNode()->GetOpacity(opacity, renderer);
property->SetOpacity(opacity);
}
Loading

0 comments on commit 4ae412d

Please sign in to comment.