diff --git a/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.cpp b/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.cpp index c1362a46e4..1574121511 100644 --- a/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.cpp +++ b/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.cpp @@ -1,691 +1,725 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkLabelSetImageVtkMapper2D.h" // MITK #include #include #include #include #include #include // MITK Rendering #include "vtkNeverTranslucentTexture.h" // VTK #include #include #include #include #include #include #include #include namespace { itk::ModifiedTimeType PropertyTimeStampIsNewer(const mitk::IPropertyProvider* provider, mitk::BaseRenderer* renderer, const std::string& propName, itk::ModifiedTimeType refMT) { const std::string context = renderer != nullptr ? renderer->GetName() : ""; auto prop = provider->GetConstProperty(propName, context); if (prop != nullptr) { return prop->GetTimeStamp() > refMT; } return false; } } mitk::LabelSetImageVtkMapper2D::LabelSetImageVtkMapper2D() { } mitk::LabelSetImageVtkMapper2D::~LabelSetImageVtkMapper2D() { } vtkProp *mitk::LabelSetImageVtkMapper2D::GetVtkProp(mitk::BaseRenderer *renderer) { // return the actor corresponding to the renderer return m_LSH.GetLocalStorage(renderer)->m_Actors; } mitk::LabelSetImageVtkMapper2D::LocalStorage *mitk::LabelSetImageVtkMapper2D::GetLocalStorage( mitk::BaseRenderer *renderer) { return m_LSH.GetLocalStorage(renderer); } void mitk::LabelSetImageVtkMapper2D::GenerateLookupTable(mitk::BaseRenderer* renderer) { LocalStorage* localStorage = m_LSH.GetLocalStorage(renderer); mitk::DataNode* node = this->GetDataNode(); auto* image = dynamic_cast(node->GetData()); assert(image && image->IsInitialized()); localStorage->m_LabelLookupTable = image->GetLookupTable()->Clone(); const auto labelValues = image->GetAllLabelValues(); std::string propertyName = "org.mitk.multilabel.labels.highlighted"; mitk::IntVectorProperty::Pointer prop = dynamic_cast(node->GetNonConstProperty(propertyName)); if (nullptr != prop) { const auto highlightedLabelValues = prop->GetValue(); if (!highlightedLabelValues.empty()) { auto lookUpTable = localStorage->m_LabelLookupTable->GetVtkLookupTable(); auto highlightEnd = highlightedLabelValues.cend(); double rgba[4]; for (const auto& value : labelValues) { lookUpTable->GetTableValue(value, rgba); if (highlightEnd == std::find(highlightedLabelValues.begin(), highlightedLabelValues.end(), value)) { //make all none highlighted values more transparent rgba[3] *= 0.3; } else if (rgba[3]!=0) { //if highlighted values are visible set them to opaque to pop out rgba[3] = 1.; } lookUpTable->SetTableValue(value, rgba); } localStorage->m_LabelLookupTable->Modified(); } } } +namespace +{ + std::vector GetOutdatedGroups(const mitk::LabelSetImageVtkMapper2D::LocalStorage* ls, const mitk::LabelSetImage* seg) + { + const auto nrOfGroups = seg->GetNumberOfLayers(); + std::vector result; + + for (mitk::LabelSetImage::GroupIndexType groupID = 0; groupID < nrOfGroups; ++groupID) + { + const auto groupImage = seg->GetGroupImage(groupID); + if (groupImage->GetMTime() > ls->m_LastDataUpdateTime + || groupImage->GetPipelineMTime() > ls->m_LastDataUpdateTime + || ls->m_GroupImageIDs.size() <= groupID + || groupImage != ls->m_GroupImageIDs[groupID]) + { + result.push_back(groupID); + } + } + return result; + } +} + void mitk::LabelSetImageVtkMapper2D::GenerateDataForRenderer(mitk::BaseRenderer *renderer) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); mitk::DataNode *node = this->GetDataNode(); auto *image = dynamic_cast(node->GetData()); assert(image && image->IsInitialized()); bool isLookupModified = localStorage->m_LabelLookupTable.IsNull() || (localStorage->m_LabelLookupTable->GetMTime() < image->GetLookupTable()->GetMTime()) || PropertyTimeStampIsNewer(node, renderer, "org.mitk.multilabel.labels.highlighted", localStorage->m_LabelLookupTable->GetMTime()); if (isLookupModified) { this->GenerateLookupTable(renderer); } - bool isDataModified = (localStorage->m_LastDataUpdateTime < image->GetMTime()) || - (localStorage->m_LastDataUpdateTime < image->GetPipelineMTime()) || - (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometryUpdateTime()) || + auto outdatedGroups = GetOutdatedGroups(localStorage, image); + + bool isGeometryModified = (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometryUpdateTime()) || (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometry()->GetMTime()); - if (isDataModified) + if (isGeometryModified) { - auto hasValidContent = this->GenerateImageSlice(renderer); - if (!hasValidContent) return; + //if geometry is outdated all groups need regeneration + outdatedGroups.resize(image->GetNumberOfLayers()); + std::iota(outdatedGroups.begin(), outdatedGroups.end(), 0); } - auto numberOfLayers = image->GetNumberOfLayers(); + if (!outdatedGroups.empty()) + { + auto hasValidContent = this->GenerateImageSlice(renderer, outdatedGroups); + if (!hasValidContent) return; + } float opacity = 1.0f; node->GetOpacity(opacity, renderer, "opacity"); - if (isDataModified && isLookupModified) + if (isLookupModified) { - + //if lookup table is modified all groups need a new color mapping + outdatedGroups.resize(image->GetNumberOfLayers()); + std::iota(outdatedGroups.begin(), outdatedGroups.end(), 0); } - for (int lidx = 0; lidx < numberOfLayers; ++lidx) + for (const auto groupID: outdatedGroups) { - localStorage->m_LayerImageMapToColors[lidx]->SetLookupTable(localStorage->m_LabelLookupTable->GetVtkLookupTable()); - localStorage->m_LayerImageMapToColors[lidx]->SetInputData(localStorage->m_ReslicedImageVector[lidx]); - localStorage->m_LayerImageMapToColors[lidx]->Update(); + localStorage->m_LayerImageMapToColors[groupID]->SetLookupTable(localStorage->m_LabelLookupTable->GetVtkLookupTable()); + localStorage->m_LayerImageMapToColors[groupID]->SetInputData(localStorage->m_ReslicedImageVector[groupID]); + localStorage->m_LayerImageMapToColors[groupID]->Update(); // check for texture interpolation property bool textureInterpolation = false; node->GetBoolProperty("texture interpolation", textureInterpolation, renderer); // set the interpolation modus according to the property - localStorage->m_LayerTextureVector[lidx]->SetInterpolate(textureInterpolation); - - //localStorage->m_LayerTextureVector[lidx]->SetInputConnection( - // localStorage->m_LevelWindowFilterVector[lidx]->GetOutputPort()); + localStorage->m_LayerTextureVector[groupID]->SetInterpolate(textureInterpolation); - localStorage->m_LayerTextureVector[lidx]->SetInputConnection( - localStorage->m_LayerImageMapToColors[lidx]->GetOutputPort()); + localStorage->m_LayerTextureVector[groupID]->SetInputConnection( + localStorage->m_LayerImageMapToColors[groupID]->GetOutputPort()); this->TransformActor(renderer); // set the plane as input for the mapper - localStorage->m_LayerMapperVector[lidx]->SetInputConnection(localStorage->m_Plane->GetOutputPort()); + localStorage->m_LayerMapperVector[groupID]->SetInputConnection(localStorage->m_Plane->GetOutputPort()); // set the texture for the actor - localStorage->m_LayerActorVector[lidx]->SetTexture(localStorage->m_LayerTextureVector[lidx]); - localStorage->m_LayerActorVector[lidx]->GetProperty()->SetOpacity(opacity); + localStorage->m_LayerActorVector[groupID]->SetTexture(localStorage->m_LayerTextureVector[groupID]); + localStorage->m_LayerActorVector[groupID]->GetProperty()->SetOpacity(opacity); } auto activeLayer = image->GetActiveLayer(); mitk::Label* activeLabel = image->GetActiveLabel(); - if (isDataModified + auto findIt = std::find(outdatedGroups.begin(), outdatedGroups.end(), activeLayer); + if (findIt != outdatedGroups.end() || PropertyTimeStampIsNewer(node, renderer, "opacity", localStorage->m_LastActiveLabelUpdateTime.GetMTime()) || PropertyTimeStampIsNewer(node, renderer, "labelset.contour.active", localStorage->m_LastActiveLabelUpdateTime.GetMTime()) || PropertyTimeStampIsNewer(node, renderer, "labelset.contour.width", localStorage->m_LastActiveLabelUpdateTime.GetMTime()) ) { this->GenerateActiveLabelOutline(renderer); } } -bool mitk::LabelSetImageVtkMapper2D::GenerateImageSlice(mitk::BaseRenderer* renderer) +bool mitk::LabelSetImageVtkMapper2D::GenerateImageSlice(mitk::BaseRenderer* renderer, const std::vector& outdatedGroupIDs) { LocalStorage* localStorage = m_LSH.GetLocalStorage(renderer); mitk::DataNode* node = this->GetDataNode(); auto* image = dynamic_cast(node->GetData()); assert(image && image->IsInitialized()); // check if there is a valid worldGeometry const PlaneGeometry* worldGeometry = renderer->GetCurrentWorldPlaneGeometry(); if ((worldGeometry == nullptr) || (!worldGeometry->IsValid()) || (!worldGeometry->HasReferenceGeometry())) return false; image->Update(); - auto numberOfLayers = image->GetNumberOfLayers(); + const auto numberOfLayers = image->GetNumberOfLayers(); if (numberOfLayers != localStorage->m_NumberOfLayers) { + if (numberOfLayers > localStorage->m_NumberOfLayers) + { + for (int lidx = localStorage->m_NumberOfLayers; lidx < numberOfLayers; ++lidx) + { + localStorage->m_GroupImageIDs.push_back(nullptr); + localStorage->m_ReslicedImageVector.push_back(vtkSmartPointer::New()); + localStorage->m_ReslicerVector.push_back(mitk::ExtractSliceFilter::New()); + localStorage->m_LayerTextureVector.push_back(vtkSmartPointer::New()); + localStorage->m_LayerMapperVector.push_back(vtkSmartPointer::New()); + localStorage->m_LayerActorVector.push_back(vtkSmartPointer::New()); + localStorage->m_LayerImageMapToColors.push_back(vtkSmartPointer::New()); + + // do not repeat the texture (the image) + localStorage->m_LayerTextureVector[lidx]->RepeatOff(); + // set corresponding mappers for the actors + localStorage->m_LayerActorVector[lidx]->SetMapper(localStorage->m_LayerMapperVector[lidx]); + } + } + else + { + localStorage->m_GroupImageIDs.resize(numberOfLayers); + localStorage->m_ReslicedImageVector.resize(numberOfLayers); + localStorage->m_ReslicerVector.resize(numberOfLayers); + localStorage->m_LayerTextureVector.resize(numberOfLayers); + localStorage->m_LayerMapperVector.resize(numberOfLayers); + localStorage->m_LayerActorVector.resize(numberOfLayers); + localStorage->m_LayerImageMapToColors.resize(numberOfLayers); + } localStorage->m_NumberOfLayers = numberOfLayers; - localStorage->m_ReslicedImageVector.clear(); - localStorage->m_ReslicerVector.clear(); - localStorage->m_LayerTextureVector.clear(); - localStorage->m_LayerMapperVector.clear(); - localStorage->m_LayerActorVector.clear(); - localStorage->m_LayerImageMapToColors.clear(); localStorage->m_Actors = vtkSmartPointer::New(); for (int lidx = 0; lidx < numberOfLayers; ++lidx) { - localStorage->m_ReslicedImageVector.push_back(vtkSmartPointer::New()); - localStorage->m_ReslicerVector.push_back(mitk::ExtractSliceFilter::New()); - localStorage->m_LayerTextureVector.push_back(vtkSmartPointer::New()); - localStorage->m_LayerMapperVector.push_back(vtkSmartPointer::New()); - localStorage->m_LayerActorVector.push_back(vtkSmartPointer::New()); - localStorage->m_LayerImageMapToColors.push_back(vtkSmartPointer::New()); - - // do not repeat the texture (the image) - localStorage->m_LayerTextureVector[lidx]->RepeatOff(); - - // set corresponding mappers for the actors - localStorage->m_LayerActorVector[lidx]->SetMapper(localStorage->m_LayerMapperVector[lidx]); - localStorage->m_Actors->AddPart(localStorage->m_LayerActorVector[lidx]); } - localStorage->m_Actors->AddPart(localStorage->m_OutlineShadowActor); localStorage->m_Actors->AddPart(localStorage->m_OutlineActor); } // early out if there is no intersection of the current rendering geometry // and the geometry of the image that is to be rendered. if (!RenderingGeometryIntersectsImage(worldGeometry, image->GetSlicedGeometry())) { // set image to nullptr, to clear the texture in 3D, because // the latest image is used there if the plane is out of the geometry // see bug-13275 for (int lidx = 0; lidx < numberOfLayers; ++lidx) { localStorage->m_ReslicedImageVector[lidx] = nullptr; localStorage->m_LayerMapperVector[lidx]->SetInputData(localStorage->m_EmptyPolyData); localStorage->m_OutlineActor->SetVisibility(false); localStorage->m_OutlineShadowActor->SetVisibility(false); } localStorage->m_LastDataUpdateTime.Modified(); return false; } - for (int lidx = 0; lidx < numberOfLayers; ++lidx) + for (const auto groupID : outdatedGroupIDs) { - const auto layerImage = image->GetGroupImage(lidx); + const auto groupImage = image->GetGroupImage(groupID); + localStorage->m_GroupImageIDs[groupID] = groupImage; - localStorage->m_ReslicerVector[lidx]->SetInput(layerImage); - localStorage->m_ReslicerVector[lidx]->SetWorldGeometry(worldGeometry); - localStorage->m_ReslicerVector[lidx]->SetTimeStep(this->GetTimestep()); + localStorage->m_ReslicerVector[groupID]->SetInput(groupImage); + localStorage->m_ReslicerVector[groupID]->SetWorldGeometry(worldGeometry); + localStorage->m_ReslicerVector[groupID]->SetTimeStep(this->GetTimestep()); // set the transformation of the image to adapt reslice axis - localStorage->m_ReslicerVector[lidx]->SetResliceTransformByGeometry( - layerImage->GetTimeGeometry()->GetGeometryForTimeStep(this->GetTimestep())); + localStorage->m_ReslicerVector[groupID]->SetResliceTransformByGeometry( + groupImage->GetTimeGeometry()->GetGeometryForTimeStep(this->GetTimestep())); // is the geometry of the slice based on the image image or the worldgeometry? bool inPlaneResampleExtentByGeometry = false; node->GetBoolProperty("in plane resample extent by geometry", inPlaneResampleExtentByGeometry, renderer); - localStorage->m_ReslicerVector[lidx]->SetInPlaneResampleExtentByGeometry(inPlaneResampleExtentByGeometry); - localStorage->m_ReslicerVector[lidx]->SetInterpolationMode(ExtractSliceFilter::RESLICE_NEAREST); - localStorage->m_ReslicerVector[lidx]->SetVtkOutputRequest(true); + localStorage->m_ReslicerVector[groupID]->SetInPlaneResampleExtentByGeometry(inPlaneResampleExtentByGeometry); + localStorage->m_ReslicerVector[groupID]->SetInterpolationMode(ExtractSliceFilter::RESLICE_NEAREST); + localStorage->m_ReslicerVector[groupID]->SetVtkOutputRequest(true); // this is needed when thick mode was enabled before. These variables have to be reset to default values - localStorage->m_ReslicerVector[lidx]->SetOutputDimensionality(2); - localStorage->m_ReslicerVector[lidx]->SetOutputSpacingZDirection(1.0); - localStorage->m_ReslicerVector[lidx]->SetOutputExtentZDirection(0, 0); + localStorage->m_ReslicerVector[groupID]->SetOutputDimensionality(2); + localStorage->m_ReslicerVector[groupID]->SetOutputSpacingZDirection(1.0); + localStorage->m_ReslicerVector[groupID]->SetOutputExtentZDirection(0, 0); // Bounds information for reslicing (only required if reference geometry is present) // this used for generating a vtkPLaneSource with the right size double sliceBounds[6]; sliceBounds[0] = 0.0; sliceBounds[1] = 0.0; sliceBounds[2] = 0.0; sliceBounds[3] = 0.0; sliceBounds[4] = 0.0; sliceBounds[5] = 0.0; - localStorage->m_ReslicerVector[lidx]->GetClippedPlaneBounds(sliceBounds); + localStorage->m_ReslicerVector[groupID]->GetClippedPlaneBounds(sliceBounds); // setup the textured plane this->GeneratePlane(renderer, sliceBounds); // get the spacing of the slice - localStorage->m_mmPerPixel = localStorage->m_ReslicerVector[lidx]->GetOutputSpacing(); - localStorage->m_ReslicerVector[lidx]->Modified(); + localStorage->m_mmPerPixel = localStorage->m_ReslicerVector[groupID]->GetOutputSpacing(); + localStorage->m_ReslicerVector[groupID]->Modified(); // start the pipeline with updating the largest possible, needed if the geometry of the image has changed - localStorage->m_ReslicerVector[lidx]->UpdateLargestPossibleRegion(); - localStorage->m_ReslicedImageVector[lidx] = localStorage->m_ReslicerVector[lidx]->GetVtkOutput(); + localStorage->m_ReslicerVector[groupID]->UpdateLargestPossibleRegion(); + localStorage->m_ReslicedImageVector[groupID] = localStorage->m_ReslicerVector[groupID]->GetVtkOutput(); } localStorage->m_LastDataUpdateTime.Modified(); return true; } void mitk::LabelSetImageVtkMapper2D::GenerateActiveLabelOutline(mitk::BaseRenderer* renderer) { LocalStorage* localStorage = m_LSH.GetLocalStorage(renderer); mitk::DataNode* node = this->GetDataNode(); auto* image = dynamic_cast(node->GetData()); int activeLayer = image->GetActiveLayer(); float opacity = 1.0f; node->GetOpacity(opacity, renderer, "opacity"); mitk::Label* activeLabel = image->GetActiveLabel(); bool contourActive = false; node->GetBoolProperty("labelset.contour.active", contourActive, renderer); if (nullptr != activeLabel && contourActive && activeLabel->GetVisible()) { //generate contours/outlines localStorage->m_OutlinePolyData = this->CreateOutlinePolyData(renderer, localStorage->m_ReslicedImageVector[activeLayer], activeLabel->GetValue()); localStorage->m_OutlineActor->SetVisibility(true); localStorage->m_OutlineShadowActor->SetVisibility(true); const mitk::Color& color = activeLabel->GetColor(); localStorage->m_OutlineActor->GetProperty()->SetColor(color.GetRed(), color.GetGreen(), color.GetBlue()); localStorage->m_OutlineShadowActor->GetProperty()->SetColor(0, 0, 0); float contourWidth(2.0); node->GetFloatProperty("labelset.contour.width", contourWidth, renderer); localStorage->m_OutlineActor->GetProperty()->SetLineWidth(contourWidth); localStorage->m_OutlineShadowActor->GetProperty()->SetLineWidth(contourWidth * 1.5); localStorage->m_OutlineActor->GetProperty()->SetOpacity(opacity); localStorage->m_OutlineShadowActor->GetProperty()->SetOpacity(opacity); localStorage->m_OutlineMapper->SetInputData(localStorage->m_OutlinePolyData); } else { localStorage->m_OutlineActor->SetVisibility(false); localStorage->m_OutlineShadowActor->SetVisibility(false); } localStorage->m_LastActiveLabelUpdateTime.Modified(); } bool mitk::LabelSetImageVtkMapper2D::RenderingGeometryIntersectsImage(const PlaneGeometry *renderingGeometry, SlicedGeometry3D *imageGeometry) { // if either one of the two geometries is nullptr we return true // for safety reasons if (renderingGeometry == nullptr || imageGeometry == nullptr) return true; // get the distance for the first cornerpoint ScalarType initialDistance = renderingGeometry->SignedDistance(imageGeometry->GetCornerPoint(0)); for (int i = 1; i < 8; i++) { mitk::Point3D cornerPoint = imageGeometry->GetCornerPoint(i); // get the distance to the other cornerpoints ScalarType distance = renderingGeometry->SignedDistance(cornerPoint); // if it has not the same signing as the distance of the first point if (initialDistance * distance < 0) { // we have an intersection and return true return true; } } // all distances have the same sign, no intersection and we return false return false; } vtkSmartPointer mitk::LabelSetImageVtkMapper2D::CreateOutlinePolyData(mitk::BaseRenderer *renderer, vtkImageData *image, int pixelValue) { LocalStorage *localStorage = this->GetLocalStorage(renderer); // get the min and max index values of each direction int *extent = image->GetExtent(); int xMin = extent[0]; int xMax = extent[1]; int yMin = extent[2]; int yMax = extent[3]; int *dims = image->GetDimensions(); // dimensions of the image int line = dims[0]; // how many pixels per line? int x = xMin; // pixel index x int y = yMin; // pixel index y // get the depth for each contour float depth = this->CalculateLayerDepth(renderer); vtkSmartPointer points = vtkSmartPointer::New(); // the points to draw vtkSmartPointer lines = vtkSmartPointer::New(); // the lines to connect the points // We take the pointer to the first pixel of the image auto *currentPixel = static_cast(image->GetScalarPointer()); while (y <= yMax) { // if the current pixel value is set to something if ((currentPixel) && (*currentPixel == pixelValue)) { // check in which direction a line is necessary // a line is added if the neighbor of the current pixel has the value 0 // and if the pixel is located at the edge of the image // if vvvvv not the first line vvvvv if (y > yMin && *(currentPixel - line) != pixelValue) { // x direction - bottom edge of the pixel // add the 2 points vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x + 1) * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); // add the line between both points lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv not the last line vvvvv if (y < yMax && *(currentPixel + line) != pixelValue) { // x direction - top edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint( (x + 1) * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv not the first pixel vvvvv if ((x > xMin || y > yMin) && *(currentPixel - 1) != pixelValue) { // y direction - left edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv not the last pixel vvvvv if ((y < yMax || (x < xMax)) && *(currentPixel + 1) != pixelValue) { // y direction - right edge of the pixel vtkIdType p1 = points->InsertNextPoint((x + 1) * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint( (x + 1) * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } /* now consider pixels at the edge of the image */ // if vvvvv left edge of image vvvvv if (x == xMin) { // draw left edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv right edge of image vvvvv if (x == xMax) { // draw right edge of the pixel vtkIdType p1 = points->InsertNextPoint((x + 1) * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint( (x + 1) * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv bottom edge of image vvvvv if (y == yMin) { // draw bottom edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x + 1) * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv top edge of image vvvvv if (y == yMax) { // draw top edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint( (x + 1) * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } } // end if currentpixel is set x++; if (x > xMax) { // reached end of line x = xMin; y++; } // Increase the pointer-position to the next pixel. // This is safe, as the while-loop and the x-reset logic above makes // sure we do not exceed the bounds of the image currentPixel++; } // end of while // Create a polydata to store everything in vtkSmartPointer polyData = vtkSmartPointer::New(); // Add the points to the dataset polyData->SetPoints(points); // Add the lines to the dataset polyData->SetLines(lines); return polyData; } void mitk::LabelSetImageVtkMapper2D::Update(mitk::BaseRenderer *renderer) { bool visible = true; const DataNode *node = this->GetDataNode(); node->GetVisibility(visible, renderer, "visible"); if (!visible) return; auto *image = dynamic_cast(node->GetData()); if (image == nullptr || image->IsInitialized() == false) return; // Calculate time step of the image data for the specified renderer (integer value) this->CalculateTimeStep(renderer); // Check if time step is valid const TimeGeometry *dataTimeGeometry = image->GetTimeGeometry(); if ((dataTimeGeometry == nullptr) || (dataTimeGeometry->CountTimeSteps() == 0) || (!dataTimeGeometry->IsValidTimeStep(this->GetTimestep()))) { return; } image->UpdateOutputInformation(); LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); // check if something important has changed and we need to re-render if (localStorage->m_LabelLookupTable.IsNull() || (localStorage->m_LabelLookupTable->GetMTime() < image->GetLookupTable()->GetMTime()) || (localStorage->m_LastDataUpdateTime < image->GetMTime()) || (localStorage->m_LastDataUpdateTime < image->GetPipelineMTime()) || (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometryUpdateTime()) || (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometry()->GetMTime()) || (localStorage->m_LastPropertyUpdateTime < node->GetPropertyList()->GetMTime()) || (localStorage->m_LastPropertyUpdateTime < node->GetPropertyList(renderer)->GetMTime()) || (localStorage->m_LastPropertyUpdateTime < image->GetPropertyList()->GetMTime())) { this->GenerateDataForRenderer(renderer); localStorage->m_LastPropertyUpdateTime.Modified(); } else if ((localStorage->m_LastPropertyUpdateTime < node->GetPropertyList()->GetMTime()) || (localStorage->m_LastPropertyUpdateTime < node->GetPropertyList(renderer)->GetMTime()) || (localStorage->m_LastPropertyUpdateTime < image->GetPropertyList()->GetMTime())) { this->GenerateDataForRenderer(renderer); localStorage->m_LastPropertyUpdateTime.Modified(); } } // set the two points defining the textured plane according to the dimension and spacing void mitk::LabelSetImageVtkMapper2D::GeneratePlane(mitk::BaseRenderer *renderer, double planeBounds[6]) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); float depth = this->CalculateLayerDepth(renderer); // Set the origin to (xMin; yMin; depth) of the plane. This is necessary for obtaining the correct // plane size in crosshair rotation and swivel mode. localStorage->m_Plane->SetOrigin(planeBounds[0], planeBounds[2], depth); // These two points define the axes of the plane in combination with the origin. // Point 1 is the x-axis and point 2 the y-axis. // Each plane is transformed according to the view (axial, coronal and sagittal) afterwards. localStorage->m_Plane->SetPoint1(planeBounds[1], planeBounds[2], depth); // P1: (xMax, yMin, depth) localStorage->m_Plane->SetPoint2(planeBounds[0], planeBounds[3], depth); // P2: (xMin, yMax, depth) } float mitk::LabelSetImageVtkMapper2D::CalculateLayerDepth(mitk::BaseRenderer *renderer) { // get the clipping range to check how deep into z direction we can render images double maxRange = renderer->GetVtkRenderer()->GetActiveCamera()->GetClippingRange()[1]; // Due to a VTK bug, we cannot use the whole clipping range. /100 is empirically determined float depth = -maxRange * 0.01; // divide by 100 int layer = 0; GetDataNode()->GetIntProperty("layer", layer, renderer); // add the layer property for each image to render images with a higher layer on top of the others depth += layer * 10; //*10: keep some room for each image (e.g. for ODFs in between) if (depth > 0.0f) { depth = 0.0f; MITK_WARN << "Layer value exceeds clipping range. Set to minimum instead."; } return depth; } void mitk::LabelSetImageVtkMapper2D::TransformActor(mitk::BaseRenderer *renderer) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); // get the transformation matrix of the reslicer in order to render the slice as axial, coronal or sagittal vtkSmartPointer trans = vtkSmartPointer::New(); vtkSmartPointer matrix = localStorage->m_ReslicerVector[0]->GetResliceAxes(); // same for all layers trans->SetMatrix(matrix); for (int lidx = 0; lidx < localStorage->m_NumberOfLayers; ++lidx) { // transform the plane/contour (the actual actor) to the corresponding view (axial, coronal or sagittal) localStorage->m_LayerActorVector[lidx]->SetUserTransform(trans); // transform the origin to center based coordinates, because MITK is center based. localStorage->m_LayerActorVector[lidx]->SetPosition( -0.5 * localStorage->m_mmPerPixel[0], -0.5 * localStorage->m_mmPerPixel[1], 0.0); } // same for outline actor localStorage->m_OutlineActor->SetUserTransform(trans); localStorage->m_OutlineActor->SetPosition( -0.5 * localStorage->m_mmPerPixel[0], -0.5 * localStorage->m_mmPerPixel[1], 0.0); // same for outline shadow actor localStorage->m_OutlineShadowActor->SetUserTransform(trans); localStorage->m_OutlineShadowActor->SetPosition( -0.5 * localStorage->m_mmPerPixel[0], -0.5 * localStorage->m_mmPerPixel[1], 0.0); } void mitk::LabelSetImageVtkMapper2D::SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer, bool overwrite) { // add/replace the following properties node->SetProperty("opacity", FloatProperty::New(1.0f), renderer); node->SetProperty("binary", BoolProperty::New(false), renderer); node->SetProperty("labelset.contour.active", BoolProperty::New(true), renderer); node->SetProperty("labelset.contour.width", FloatProperty::New(2.0), renderer); Superclass::SetDefaultProperties(node, renderer, overwrite); } mitk::LabelSetImageVtkMapper2D::LocalStorage::~LocalStorage() { } mitk::LabelSetImageVtkMapper2D::LocalStorage::LocalStorage() { // Do as much actions as possible in here to avoid double executions. m_Plane = vtkSmartPointer::New(); m_Actors = vtkSmartPointer::New(); m_OutlinePolyData = vtkSmartPointer::New(); m_EmptyPolyData = vtkSmartPointer::New(); m_OutlineActor = vtkSmartPointer::New(); m_OutlineMapper = vtkSmartPointer::New(); m_OutlineShadowActor = vtkSmartPointer::New(); m_NumberOfLayers = 0; m_mmPerPixel = nullptr; m_OutlineActor->SetMapper(m_OutlineMapper); m_OutlineShadowActor->SetMapper(m_OutlineMapper); m_OutlineActor->SetVisibility(false); m_OutlineShadowActor->SetVisibility(false); - -// m_LabelLookupTable = mitk::LookupTable::New(); } diff --git a/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.h b/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.h index a79bf613fc..dff08ed03c 100644 --- a/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.h +++ b/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.h @@ -1,222 +1,228 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkLabelSetImageVtkMapper2D_h #define mitkLabelSetImageVtkMapper2D_h // MITK #include "MitkMultilabelExports.h" #include "mitkCommon.h" // MITK Rendering #include "mitkBaseRenderer.h" #include "mitkExtractSliceFilter.h" #include "mitkLabelSetImage.h" #include "mitkVtkMapper.h" // VTK #include class vtkActor; class vtkPolyDataMapper; class vtkPlaneSource; class vtkImageData; class vtkLookupTable; class vtkImageReslice; class vtkPoints; class vtkMitkThickSlicesFilter; class vtkPolyData; class vtkNeverTranslucentTexture; class vtkImageMapToColors; namespace mitk { /** \brief Mapper to resample and display 2D slices of a 3D labelset image. * * Properties that can be set for labelset images and influence this mapper are: * * - \b "labelset.contour.active": (BoolProperty) whether to show only the active label as a contour or not * - \b "labelset.contour.width": (FloatProperty) line width of the contour * The default properties are: * - \b "labelset.contour.active", mitk::BoolProperty::New( true ), renderer, overwrite ) * - \b "labelset.contour.width", mitk::FloatProperty::New( 2.0 ), renderer, overwrite ) * \ingroup Mapper */ class MITKMULTILABEL_EXPORT LabelSetImageVtkMapper2D : public VtkMapper { public: /** Standard class typedefs. */ mitkClassMacro(LabelSetImageVtkMapper2D, VtkMapper); /** Method for creation through the object factory. */ itkNewMacro(Self); /** \brief Get the Image to map */ const mitk::Image *GetInput(void); /** \brief Checks whether this mapper needs to update itself and generate * data. */ void Update(mitk::BaseRenderer *renderer) override; //### methods of MITK-VTK rendering pipeline vtkProp *GetVtkProp(mitk::BaseRenderer *renderer) override; //### end of methods of MITK-VTK rendering pipeline /** \brief Internal class holding the mapper, actor, etc. for each of the 3 2D render windows */ /** * To render axial, coronal, and sagittal, the mapper is called three times. * For performance reasons, the corresponding data for each view is saved in the * internal helper class LocalStorage. This allows rendering n views with just * 1 mitkMapper using n vtkMapper. * */ class MITKMULTILABEL_EXPORT LocalStorage : public mitk::Mapper::BaseLocalStorage { public: vtkSmartPointer m_Actors; + /** Vector containing the pointer of the currently used group images. + * IMPORTANT: This member must not be used to access any data. + * Its purpose is to allow checking if the order of the groups has changed + * in order to adapt the pipe line accordingly*/ + std::vector m_GroupImageIDs; + std::vector> m_LayerActorVector; std::vector> m_LayerMapperVector; std::vector> m_ReslicedImageVector; std::vector> m_LayerImageMapToColors; std::vector> m_LayerTextureVector; vtkSmartPointer m_EmptyPolyData; vtkSmartPointer m_Plane; std::vector m_ReslicerVector; vtkSmartPointer m_OutlinePolyData; /** \brief An actor for the outline */ vtkSmartPointer m_OutlineActor; /** \brief An actor for the outline shadow*/ vtkSmartPointer m_OutlineShadowActor; /** \brief A mapper for the outline */ vtkSmartPointer m_OutlineMapper; /** \brief Timestamp of last update of stored data. */ itk::TimeStamp m_LastDataUpdateTime; /** \brief Timestamp of last update of a property. */ itk::TimeStamp m_LastPropertyUpdateTime; /** \brief Timestamp of last update of a property. */ itk::TimeStamp m_LastActiveLabelUpdateTime; /** \brief mmPerPixel relation between pixel and mm. (World spacing).*/ mitk::ScalarType *m_mmPerPixel; /** look up table for label colors. */ mitk::LookupTable::Pointer m_LabelLookupTable; int m_NumberOfLayers; /** \brief Default constructor of the local storage. */ LocalStorage(); - /** \brief Default deconstructor of the local storage. */ + /** \brief Default destructor of the local storage. */ ~LocalStorage() override; }; /** \brief The LocalStorageHandler holds all (three) LocalStorages for the three 2D render windows. */ mitk::LocalStorageHandler m_LSH; /** \brief Get the LocalStorage corresponding to the current renderer. */ LocalStorage *GetLocalStorage(mitk::BaseRenderer *renderer); /** \brief Set the default properties for general image rendering. */ static void SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer = nullptr, bool overwrite = false); /** \brief This method switches between different rendering modes (e.g. use a lookup table or a transfer function). * Detailed documentation about the modes can be found here: \link mitk::RenderingModeProperty \endlink */ void ApplyRenderingMode(mitk::BaseRenderer *renderer); protected: /** \brief Transforms the actor to the actual position in 3D. * \param renderer The current renderer corresponding to the render window. */ void TransformActor(mitk::BaseRenderer *renderer); /** \brief Generates a plane according to the size of the resliced image in milimeters. * * In VTK a vtkPlaneSource is defined through three points. The origin and two * points defining the axes of the plane (see VTK documentation). The origin is * set to (xMin; yMin; Z), where xMin and yMin are the minimal bounds of the * resliced image in space. Z is relevant for blending and the layer property. * The center of the plane (C) is also the center of the view plane (cf. the image above). * * \note For the standard MITK view with three 2D render windows showing three * different slices, three such planes are generated. All these planes are generated * in the XY-plane (even if they depict a YZ-slice of the volume). * */ void GeneratePlane(mitk::BaseRenderer *renderer, double planeBounds[6]); /** \brief Generates a vtkPolyData object containing the outline of a given binary slice. \param renderer Pointer to the renderer containing the needed information \param image \param pixelValue \note This code is based on code from the iil library. */ vtkSmartPointer CreateOutlinePolyData(mitk::BaseRenderer *renderer, vtkImageData *image, int pixelValue = 1); /** Default constructor */ LabelSetImageVtkMapper2D(); /** Default deconstructor */ ~LabelSetImageVtkMapper2D() override; /** \brief Does the actual resampling, without rendering the image yet. * All the data is generated inside this method. The vtkProp (or Actor) * is filled with content (i.e. the resliced image). * * After generation, a 4x4 transformation matrix(t) of the current slice is obtained * from the vtkResliceImage object via GetReslicesAxis(). This matrix is * applied to each textured plane (actor->SetUserTransform(t)) to transform everything * to the actual 3D position (cf. the following image). * * \image html cameraPositioning3D.png * */ void GenerateDataForRenderer(mitk::BaseRenderer *renderer) override; - bool GenerateImageSlice(mitk::BaseRenderer* renderer); + bool GenerateImageSlice(mitk::BaseRenderer* renderer, const std::vector& outdatedGroupIDs); void GenerateActiveLabelOutline(mitk::BaseRenderer* renderer); /** \brief Generates the look up table that should be used. */ void GenerateLookupTable(mitk::BaseRenderer* renderer); /** \brief This method uses the vtkCamera clipping range and the layer property * to calcualte the depth of the object (e.g. image or contour). The depth is used * to keep the correct order for the final VTK rendering.*/ float CalculateLayerDepth(mitk::BaseRenderer *renderer); /** * \brief Calculates whether the given rendering geometry intersects the * given SlicedGeometry3D. * * This method checks if the given Geometry2D intersects the given * SlicedGeometry3D. It calculates the distance of the Geometry2D to all * 8 cornerpoints of the SlicedGeometry3D. If all distances have the same * sign (all positive or all negative) there is no intersection. * If the distances have different sign, there is an intersection. **/ bool RenderingGeometryIntersectsImage(const PlaneGeometry *renderingGeometry, SlicedGeometry3D *imageGeometry); }; } // namespace mitk #endif diff --git a/Modules/Multilabel/mitkMultiLabelSegmentationVtkMapper3D.cpp b/Modules/Multilabel/mitkMultiLabelSegmentationVtkMapper3D.cpp index 8ee81abe7d..a3765d5512 100644 --- a/Modules/Multilabel/mitkMultiLabelSegmentationVtkMapper3D.cpp +++ b/Modules/Multilabel/mitkMultiLabelSegmentationVtkMapper3D.cpp @@ -1,279 +1,326 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkMultiLabelSegmentationVtkMapper3D.h" // MITK #include #include #include // MITK Rendering // VTK #include #include #include #include #include #include #include #include #include #include namespace { itk::ModifiedTimeType PropertyTimeStampIsNewer(const mitk::IPropertyProvider* provider, mitk::BaseRenderer* renderer, const std::string& propName, itk::ModifiedTimeType refMT) { const std::string context = renderer != nullptr ? renderer->GetName() : ""; auto prop = provider->GetConstProperty(propName, context); if (prop != nullptr) { return prop->GetTimeStamp() > refMT; } return false; } } mitk::MultiLabelSegmentationVtkMapper3D::MultiLabelSegmentationVtkMapper3D() { } mitk::MultiLabelSegmentationVtkMapper3D::~MultiLabelSegmentationVtkMapper3D() { } vtkProp *mitk::MultiLabelSegmentationVtkMapper3D::GetVtkProp(mitk::BaseRenderer *renderer) { // return the actor corresponding to the renderer return m_LSH.GetLocalStorage(renderer)->m_Actors; } mitk::MultiLabelSegmentationVtkMapper3D::LocalStorage *mitk::MultiLabelSegmentationVtkMapper3D::GetLocalStorage( mitk::BaseRenderer *renderer) { return m_LSH.GetLocalStorage(renderer); } void mitk::MultiLabelSegmentationVtkMapper3D::GenerateLookupTable(mitk::BaseRenderer* renderer) { LocalStorage* localStorage = m_LSH.GetLocalStorage(renderer); mitk::DataNode* node = this->GetDataNode(); auto* image = dynamic_cast(node->GetData()); assert(image && image->IsInitialized()); localStorage->m_LabelLookupTable = image->GetLookupTable()->Clone(); + auto lookUpTable = localStorage->m_LabelLookupTable->GetVtkLookupTable(); + const auto labelValues = image->GetAllLabelValues(); std::string propertyName = "org.mitk.multilabel.labels.highlighted"; mitk::IntVectorProperty::Pointer prop = dynamic_cast(node->GetNonConstProperty(propertyName)); if (nullptr != prop) { const auto highlightedLabelValues = prop->GetValue(); if (!highlightedLabelValues.empty()) { - auto lookUpTable = localStorage->m_LabelLookupTable->GetVtkLookupTable(); auto highlightEnd = highlightedLabelValues.cend(); double rgba[4]; for (const auto& value : labelValues) { lookUpTable->GetTableValue(value, rgba); if (highlightEnd == std::find(highlightedLabelValues.begin(), highlightedLabelValues.end(), value)) { //make all none highlighted values more transparent - rgba[3] *= 0.15; + rgba[3] *= 0.05; } else if (rgba[3] != 0) { //if highlighted values are visible set them to opaque to pop out rgba[3] = 1.; } lookUpTable->SetTableValue(value, rgba); } localStorage->m_LabelLookupTable->Modified(); // need to call modified, since LookupTableProperty seems to be unchanged so no widget-update is // executed + } + } + localStorage->m_TransferFunction = vtkSmartPointer::New(); + localStorage->m_OpacityTransferFunction = vtkSmartPointer::New(); - localStorage->m_TransferFunction = vtkSmartPointer::New(); - localStorage->m_OpacityTransferFunction = vtkSmartPointer::New(); + localStorage->m_TransferFunction->AddRGBPoint(0, 0., 0., 1.); + localStorage->m_OpacityTransferFunction->AddPoint(0, 0.); - localStorage->m_TransferFunction->AddRGBPoint(0, 0., 0., 1.); - localStorage->m_OpacityTransferFunction->AddPoint(0, 0.); + for (const auto& value : labelValues) + { + double* color = lookUpTable->GetTableValue(value); + localStorage->m_TransferFunction->AddRGBPoint(value, color[0], color[1], color[2]); - for (const auto& value : labelValues) - { - double* color = lookUpTable->GetTableValue(value); - localStorage->m_TransferFunction->AddRGBPoint(value, color[0], color[1], color[2]); + localStorage->m_OpacityTransferFunction->AddPoint(value, color[3]); + } +} - localStorage->m_OpacityTransferFunction->AddPoint(value, color[3]); +namespace +{ + std::vector GetOutdatedGroups(const mitk::MultiLabelSegmentationVtkMapper3D::LocalStorage* ls, const mitk::LabelSetImage* seg) + { + const auto nrOfGroups = seg->GetNumberOfLayers(); + std::vector result; + + for (mitk::LabelSetImage::GroupIndexType groupID = 0; groupID < nrOfGroups; ++groupID) + { + const auto groupImage = seg->GetGroupImage(groupID); + if (groupImage->GetMTime() > ls->m_LastDataUpdateTime + || groupImage->GetPipelineMTime() > ls->m_LastDataUpdateTime + || ls->m_GroupImageIDs.size() <= groupID + || groupImage != ls->m_GroupImageIDs[groupID]) + { + result.push_back(groupID); } } + return result; } } void mitk::MultiLabelSegmentationVtkMapper3D::GenerateDataForRenderer(mitk::BaseRenderer *renderer) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); mitk::DataNode *node = this->GetDataNode(); auto *image = dynamic_cast(node->GetData()); assert(image && image->IsInitialized()); bool isLookupModified = localStorage->m_LabelLookupTable.IsNull() || (localStorage->m_LabelLookupTable->GetMTime() < image->GetLookupTable()->GetMTime()) || PropertyTimeStampIsNewer(node, renderer, "org.mitk.multilabel.labels.highlighted", localStorage->m_LabelLookupTable->GetMTime()); if (isLookupModified) { this->GenerateLookupTable(renderer); } + + auto outdatedGroups = GetOutdatedGroups(localStorage, image); - bool isDataModified = (localStorage->m_LastDataUpdateTime < image->GetMTime()) || - (localStorage->m_LastDataUpdateTime < image->GetPipelineMTime()) || - (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometryUpdateTime()) || + bool isGeometryModified = (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometryUpdateTime()) || (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometry()->GetMTime()); - if (isDataModified) + if (isGeometryModified) { - auto hasValidContent = this->GenerateVolumeMapping(renderer); + //if geometry is outdated all groups need regeneration + outdatedGroups.resize(image->GetNumberOfLayers()); + std::iota(outdatedGroups.begin(), outdatedGroups.end(), 0); + } + + if (!outdatedGroups.empty()) + { + auto hasValidContent = this->GenerateVolumeMapping(renderer, outdatedGroups); if (!hasValidContent) return; } float opacity = 1.0f; node->GetOpacity(opacity, renderer, "opacity"); - auto numberOfLayers = image->GetNumberOfLayers(); - for (int lidx = 0; lidx < numberOfLayers; ++lidx) + if (isLookupModified) { - localStorage->m_LayerVolumeProperties[lidx]->SetColor(localStorage->m_TransferFunction); - localStorage->m_LayerVolumeProperties[lidx]->SetScalarOpacity(localStorage->m_OpacityTransferFunction); - localStorage->m_LayerVolumes[lidx]->SetMapper(localStorage->m_LayerVolumeMappers[lidx]); - localStorage->m_LayerVolumes[lidx]->SetProperty(localStorage->m_LayerVolumeProperties[lidx]); + //if lookup table is modified all groups need a new color mapping + outdatedGroups.resize(image->GetNumberOfLayers()); + std::iota(outdatedGroups.begin(), outdatedGroups.end(), 0); + } + + for (const auto groupID : outdatedGroups) + { + localStorage->m_LayerVolumeProperties[groupID]->SetColor(localStorage->m_TransferFunction); + localStorage->m_LayerVolumeProperties[groupID]->SetScalarOpacity(localStorage->m_OpacityTransferFunction); + localStorage->m_LayerVolumes[groupID]->SetMapper(localStorage->m_LayerVolumeMappers[groupID]); + localStorage->m_LayerVolumes[groupID]->SetProperty(localStorage->m_LayerVolumeProperties[groupID]); } } -bool mitk::MultiLabelSegmentationVtkMapper3D::GenerateVolumeMapping(mitk::BaseRenderer* renderer) +bool mitk::MultiLabelSegmentationVtkMapper3D::GenerateVolumeMapping(mitk::BaseRenderer* renderer, const std::vector& outdatedGroupIDs) { LocalStorage* localStorage = m_LSH.GetLocalStorage(renderer); mitk::DataNode* node = this->GetDataNode(); auto* image = dynamic_cast(node->GetData()); assert(image && image->IsInitialized()); image->Update(); - auto numberOfLayers = image->GetNumberOfLayers(); + const auto numberOfLayers = image->GetNumberOfLayers(); if (numberOfLayers != localStorage->m_NumberOfLayers) { + if (numberOfLayers > localStorage->m_NumberOfLayers) + { + for (int lidx = localStorage->m_NumberOfLayers; lidx < numberOfLayers; ++lidx) + { + localStorage->m_GroupImageIDs.push_back(nullptr); + localStorage->m_LayerImages.push_back(vtkSmartPointer::New()); + localStorage->m_LayerVolumeMappers.push_back(vtkSmartPointer::New()); + localStorage->m_LayerVolumeProperties.push_back(vtkSmartPointer::New()); + localStorage->m_LayerVolumes.push_back(vtkSmartPointer::New()); + } + } + else + { + localStorage->m_GroupImageIDs.resize(numberOfLayers); + localStorage->m_LayerImages.resize(numberOfLayers); + localStorage->m_LayerVolumeMappers.resize(numberOfLayers); + localStorage->m_LayerVolumeProperties.resize(numberOfLayers); + localStorage->m_LayerVolumes.resize(numberOfLayers); + } + localStorage->m_NumberOfLayers = numberOfLayers; - localStorage->m_LayerImages.clear(); - localStorage->m_LayerVolumeMappers.clear(); - localStorage->m_LayerVolumeProperties.clear(); - localStorage->m_LayerVolumes.clear(); localStorage->m_Actors = vtkSmartPointer::New(); for (int lidx = 0; lidx < numberOfLayers; ++lidx) { - localStorage->m_LayerImages.push_back(vtkSmartPointer::New()); - localStorage->m_LayerVolumeMappers.push_back(vtkSmartPointer::New()); - localStorage->m_LayerVolumeProperties.push_back(vtkSmartPointer::New()); - localStorage->m_LayerVolumes.push_back(vtkSmartPointer::New()); - localStorage->m_Actors->AddPart(localStorage->m_LayerVolumes[lidx]); } } - for (int lidx = 0; lidx < numberOfLayers; ++lidx) + for (const auto groupID : outdatedGroupIDs) { - const auto layerImage = image->GetGroupImage(lidx); + const auto groupImage = image->GetGroupImage(groupID); + localStorage->m_GroupImageIDs[groupID] = groupImage; - localStorage->m_LayerImages[lidx] = layerImage->GetVtkImageData(this->GetTimestep()); - localStorage->m_LayerVolumeMappers[lidx]->SetInputData(localStorage->m_LayerImages[lidx]); - localStorage->m_LayerVolumeProperties[lidx]->ShadeOff(); - localStorage->m_LayerVolumeProperties[lidx]->SetInterpolationTypeToNearest(); + localStorage->m_LayerImages[groupID] = groupImage->GetVtkImageData(this->GetTimestep()); + localStorage->m_LayerVolumeMappers[groupID]->SetInputData(localStorage->m_LayerImages[groupID]); + localStorage->m_LayerVolumeProperties[groupID]->ShadeOff(); + localStorage->m_LayerVolumeProperties[groupID]->SetInterpolationTypeToNearest(); } localStorage->m_LastDataUpdateTime.Modified(); return true; } void mitk::MultiLabelSegmentationVtkMapper3D::Update(mitk::BaseRenderer *renderer) { bool visible = true; const DataNode *node = this->GetDataNode(); node->GetVisibility(visible, renderer, "visible"); if (!visible) return; auto *image = dynamic_cast(node->GetData()); if (image == nullptr || image->IsInitialized() == false) return; // Calculate time step of the image data for the specified renderer (integer value) this->CalculateTimeStep(renderer); // Check if time step is valid const TimeGeometry *dataTimeGeometry = image->GetTimeGeometry(); if ((dataTimeGeometry == nullptr) || (dataTimeGeometry->CountTimeSteps() == 0) || (!dataTimeGeometry->IsValidTimeStep(this->GetTimestep()))) { return; } image->UpdateOutputInformation(); LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); // check if something important has changed and we need to re-render if (localStorage->m_LabelLookupTable.IsNull() || (localStorage->m_LabelLookupTable->GetMTime() < image->GetLookupTable()->GetMTime()) || (localStorage->m_LastDataUpdateTime < image->GetMTime()) || (localStorage->m_LastDataUpdateTime < image->GetPipelineMTime()) || (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometryUpdateTime()) || (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometry()->GetMTime()) || (localStorage->m_LastPropertyUpdateTime < node->GetPropertyList()->GetMTime()) || (localStorage->m_LastPropertyUpdateTime < node->GetPropertyList(renderer)->GetMTime()) || (localStorage->m_LastPropertyUpdateTime < image->GetPropertyList()->GetMTime())) { this->GenerateDataForRenderer(renderer); localStorage->m_LastPropertyUpdateTime.Modified(); } } void mitk::MultiLabelSegmentationVtkMapper3D::SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer, bool overwrite) { // add/replace the following properties node->SetProperty("opacity", FloatProperty::New(1.0f), renderer); node->SetProperty("labelset.contour.active", BoolProperty::New(true), renderer); node->SetProperty("labelset.contour.width", FloatProperty::New(2.0), renderer); Superclass::SetDefaultProperties(node, renderer, overwrite); } mitk::MultiLabelSegmentationVtkMapper3D::LocalStorage::~LocalStorage() { } mitk::MultiLabelSegmentationVtkMapper3D::LocalStorage::LocalStorage() { // Do as much actions as possible in here to avoid double executions. m_Actors = vtkSmartPointer::New(); m_NumberOfLayers = 0; } diff --git a/Modules/Multilabel/mitkMultiLabelSegmentationVtkMapper3D.h b/Modules/Multilabel/mitkMultiLabelSegmentationVtkMapper3D.h index 0c0ab5ebf1..4d9aefeec1 100644 --- a/Modules/Multilabel/mitkMultiLabelSegmentationVtkMapper3D.h +++ b/Modules/Multilabel/mitkMultiLabelSegmentationVtkMapper3D.h @@ -1,143 +1,149 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkMultiLabelSegmentationVtkMapper3D_h #define mitkMultiLabelSegmentationVtkMapper3D_h // MITK #include "MitkMultilabelExports.h" #include "mitkCommon.h" // MITK Rendering #include "mitkBaseRenderer.h" #include "mitkExtractSliceFilter.h" #include "mitkLabelSetImage.h" #include "mitkVtkMapper.h" // VTK #include class vtkPolyDataMapper; class vtkImageData; class vtkLookupTable; class vtkVolumeProperty; class vtkVolume; class vtkSmartVolumeMapper; namespace mitk { /** \brief Mapper to resample and display 2D slices of a 3D labelset image. * * Properties that can be set for labelset images and influence this mapper are: * * - \b "labelset.contour.active": (BoolProperty) whether to show only the active label as a contour or not * - \b "labelset.contour.width": (FloatProperty) line width of the contour * The default properties are: * - \b "labelset.contour.active", mitk::BoolProperty::New( true ), renderer, overwrite ) * - \b "labelset.contour.width", mitk::FloatProperty::New( 2.0 ), renderer, overwrite ) * \ingroup Mapper */ class MITKMULTILABEL_EXPORT MultiLabelSegmentationVtkMapper3D : public VtkMapper { public: /** Standard class typedefs. */ mitkClassMacro(MultiLabelSegmentationVtkMapper3D, VtkMapper); /** Method for creation through the object factory. */ itkNewMacro(Self); /** \brief Checks whether this mapper needs to update itself and generate * data. */ void Update(mitk::BaseRenderer *renderer) override; //### methods of MITK-VTK rendering pipeline vtkProp *GetVtkProp(mitk::BaseRenderer *renderer) override; //### end of methods of MITK-VTK rendering pipeline /** \brief Internal class holding the mapper, actor, etc. for each of the 3 2D render windows */ /** * To render axial, coronal, and sagittal, the mapper is called three times. * For performance reasons, the corresponding data for each view is saved in the * internal helper class LocalStorage. This allows rendering n views with just * 1 mitkMapper using n vtkMapper. * */ class MITKMULTILABEL_EXPORT LocalStorage : public mitk::Mapper::BaseLocalStorage { public: vtkSmartPointer m_Actors; std::vector> m_LayerVolumeMappers; std::vector> m_LayerImages; std::vector> m_LayerVolumes; std::vector> m_LayerVolumeProperties; + /** Vector containing the pointer of the currently used group images. + * IMPORTANT: This member must not be used to access any data. + * Its purpose is to allow checking if the order of the groups has changed + * in order to adapt the pipe line accordingly*/ + std::vector m_GroupImageIDs; + /** \brief Timestamp of last update of stored data. */ itk::TimeStamp m_LastDataUpdateTime; /** \brief Timestamp of last update of a property. */ itk::TimeStamp m_LastPropertyUpdateTime; /** look up table for label colors. */ mitk::LookupTable::Pointer m_LabelLookupTable; vtkSmartPointer m_TransferFunction; vtkSmartPointer m_OpacityTransferFunction; int m_NumberOfLayers; /** \brief Default constructor of the local storage. */ LocalStorage(); - /** \brief Default deconstructor of the local storage. */ + /** \brief Default destructor of the local storage. */ ~LocalStorage() override; }; /** \brief The LocalStorageHandler holds all (three) LocalStorages for the three 2D render windows. */ mitk::LocalStorageHandler m_LSH; /** \brief Get the LocalStorage corresponding to the current renderer. */ LocalStorage *GetLocalStorage(mitk::BaseRenderer *renderer); /** \brief Set the default properties for general image rendering. */ static void SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer = nullptr, bool overwrite = false); protected: /** Default constructor */ MultiLabelSegmentationVtkMapper3D(); /** Default deconstructor */ ~MultiLabelSegmentationVtkMapper3D() override; /** \brief Does the actual resampling, without rendering the image yet. * All the data is generated inside this method. The vtkProp (or Actor) * is filled with content (i.e. the resliced image). * * After generation, a 4x4 transformation matrix(t) of the current slice is obtained * from the vtkResliceImage object via GetReslicesAxis(). This matrix is * applied to each textured plane (actor->SetUserTransform(t)) to transform everything * to the actual 3D position (cf. the following image). * * \image html cameraPositioning3D.png * */ void GenerateDataForRenderer(mitk::BaseRenderer *renderer) override; - bool GenerateVolumeMapping(mitk::BaseRenderer* renderer); + bool GenerateVolumeMapping(mitk::BaseRenderer* renderer, const std::vector& outdatedGroupIDs); /** \brief Generates the look up table that should be used. */ void GenerateLookupTable(mitk::BaseRenderer* renderer); }; } // namespace mitk #endif