From 59a9e829fc926e083029d42985f106fd1b0c3711 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Thu, 2 Apr 2026 14:08:32 +0000 Subject: [PATCH] refactor: enhance vtkVoxImage volume rendering with dynamic shader range scaling, improved transfer function management, and synchronized VTK property updates. --- app/gcompose/src/PropertyWidgets.cpp | 12 +-- src/Vtk/Math/vtkContainerBox.cpp | 10 +- src/Vtk/Math/vtkVoxImage.cpp | 149 ++++++++++++++++----------- src/Vtk/Math/vtkVoxImage.h | 11 ++ src/Vtk/uLibVtkInterface.cxx | 6 +- src/Vtk/vtkObjectsContext.cpp | 9 +- 6 files changed, 119 insertions(+), 78 deletions(-) diff --git a/app/gcompose/src/PropertyWidgets.cpp b/app/gcompose/src/PropertyWidgets.cpp index 3c87bf9..15dc1be 100644 --- a/app/gcompose/src/PropertyWidgets.cpp +++ b/app/gcompose/src/PropertyWidgets.cpp @@ -448,17 +448,17 @@ void PropertyEditor::setObject(::uLib::Object* obj, bool displayOnly) { } } else { // Priority 2: Standard factory lookup - auto it = m_Factories.find(prop->GetTypeIndex()); - if (it != m_Factories.end()) { - widget = it->second(prop, m_Container); - } else { + auto it = m_Factories.find(prop->GetTypeIndex()); + if (it != m_Factories.end()) { + widget = it->second(prop, m_Container); + } else { // Debug info for unknown types std::cout << "PropertyEditor: No factory for " << prop->GetQualifiedName() << " (Type: " << prop->GetTypeName() << ")" << std::endl; - widget = new PropertyWidgetBase(prop, m_Container); + widget = new PropertyWidgetBase(prop, m_Container); widget->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")")); - } + } } if (widget) { diff --git a/src/Vtk/Math/vtkContainerBox.cpp b/src/Vtk/Math/vtkContainerBox.cpp index 9c7b5aa..74640fb 100644 --- a/src/Vtk/Math/vtkContainerBox.cpp +++ b/src/Vtk/Math/vtkContainerBox.cpp @@ -111,17 +111,17 @@ void vtkContainerBox::SyncFromVtk() { // VTK -> Model: Extract new world TRS from proxy, which matches the model's TRS center vtkMatrix4x4* rootMat = root->GetUserMatrix(); - if (rootMat) { - std::cout << "[vtkContainerBox::SyncFromVtk] Read Proxy UserMatrix:" << std::endl; - rootMat->Print(std::cout); - } + // if (rootMat) { + // std::cout << "[vtkContainerBox::SyncFromVtk] Read Proxy UserMatrix:" << std::endl; + // rootMat->Print(std::cout); + // } Matrix4f vtkWorld = VtkToMatrix4f(rootMat); // Synchronize TRS property members from the updated local matrix m_Content->FromMatrix(vtkWorld); - std::cout << "[vtkContainerBox::SyncFromVtk] New Model WorldMatrix:" << std::endl << m_Content->GetWorldMatrix() << std::endl; + // std::cout << "[vtkContainerBox::SyncFromVtk] New Model WorldMatrix:" << std::endl << m_Content->GetWorldMatrix() << std::endl; // Since we modified the model, notify observers, but block the loop back to VTK // ConnectionBlock blocker(d->m_UpdateSignal); diff --git a/src/Vtk/Math/vtkVoxImage.cpp b/src/Vtk/Math/vtkVoxImage.cpp index a266621..87e454c 100644 --- a/src/Vtk/Math/vtkVoxImage.cpp +++ b/src/Vtk/Math/vtkVoxImage.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -49,6 +50,7 @@ #include #include "Vtk/Math/vtkVoxImage.h" +#include "Vtk/Math/vtkDense.h" #include VTK_MODULE_INIT(vtkRenderingVolumeOpenGL2); @@ -128,10 +130,15 @@ vtkVoxImage::vtkVoxImage(Content &content) m_Image(vtkImageData::New()), m_Outline(vtkCubeSource::New()), m_OutlineActor(vtkActor::New()), m_Reader(NULL), m_Writer(NULL), writer_factor(1.E6), - m_Window(40/1.E6), m_Level(20/1.E6), m_ShadingPreset(0) { + m_Window(1.0), m_Level(0.5), m_ShadingPreset(0) { + // Transfer functions + m_ColorFun = vtkColorTransferFunction::New(); + m_OpacityFun = vtkPiecewiseFunction::New(); + m_UpdateConnection = Object::connect(&m_Content, &uLib::Object::Updated, this, &vtkVoxImage::Update); + GetContent(); InstallPipe(); - ULIB_ACTIVATE_DISPLAY_PROPERTIES; + RescaleShaderRange(); } vtkVoxImage::~vtkVoxImage() { @@ -140,6 +147,8 @@ vtkVoxImage::~vtkVoxImage() { m_Asm->Delete(); m_Outline->Delete(); m_OutlineActor->Delete(); + m_ColorFun->Delete(); + m_OpacityFun->Delete(); } vtkImageData *vtkVoxImage::GetImageData() { @@ -181,6 +190,7 @@ void vtkVoxImage::ReadFromVKTFile(const char *fname) { m_Image->DeepCopy(vtkscale->GetOutput()); SetContent(); + RescaleShaderRange(); } else { std::cerr << "Error: file does not contain structured points\n"; } @@ -200,115 +210,134 @@ void vtkVoxImage::ReadFromXMLFile(const char *fname) { m_Image->DeepCopy(vtkscale->GetOutput()); SetContent(); + RescaleShaderRange(); } void vtkVoxImage::setShadingPreset(int blendType) { m_ShadingPreset = blendType; vtkSmartVolumeMapper *mapper = (vtkSmartVolumeMapper *)m_Actor->GetMapper(); + if (!mapper) return; vtkVolumeProperty *property = m_Actor->GetProperty(); - static vtkColorTransferFunction *colorFun = vtkColorTransferFunction::New(); - static vtkPiecewiseFunction *opacityFun = vtkPiecewiseFunction::New(); - float window = m_Window; float level = m_Level; - property->SetColor(colorFun); - property->SetScalarOpacity(opacityFun); + property->SetColor(m_ColorFun); + property->SetScalarOpacity(m_OpacityFun); property->SetInterpolationTypeToLinear(); - if (blendType != 6) { - colorFun->RemoveAllPoints(); - opacityFun->RemoveAllPoints(); - } + m_ColorFun->RemoveAllPoints(); + m_OpacityFun->RemoveAllPoints(); switch (blendType) { - case 0: - colorFun->AddRGBSegment(0.0, 1.0, 1.0, 1.0, 255.0, 1.0, 1.0, 1.0); - opacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, - 1.0); + case 0: // MIP + m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 0); + m_ColorFun->AddRGBPoint(level + 0.5 * window, 1, 1, 1); + m_OpacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0); mapper->SetBlendModeToMaximumIntensity(); break; - case 1: - colorFun->AddRGBSegment(level - 0.5 * window, 0.0, 0.0, 0.0, - level + 0.5 * window, 1.0, 1.0, 1.0); - opacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, - 1.0); + case 1: // Composite + m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 0); + m_ColorFun->AddRGBPoint(level + 0.5 * window, 1, 1, 1); + m_OpacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0); mapper->SetBlendModeToComposite(); property->ShadeOff(); break; - case 2: - colorFun->AddRGBSegment(0.0, 1.0, 1.0, 1.0, 255.0, 1.0, 1.0, 1.0); - opacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, - 1.0); + case 2: // Composite Shaded + m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 0); + m_ColorFun->AddRGBPoint(level + 0.5 * window, 1, 1, 1); + m_OpacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0); mapper->SetBlendModeToComposite(); property->ShadeOn(); break; - case 3: - colorFun->AddRGBPoint(-3024, 0, 0, 0, 0.5, 0.0); - colorFun->AddRGBPoint(-1000, .62, .36, .18, 0.5, 0.0); - colorFun->AddRGBPoint(-500, .88, .60, .29, 0.33, 0.45); - colorFun->AddRGBPoint(3071, .83, .66, 1, 0.5, 0.0); - opacityFun->AddPoint(-3024, 0, 0.5, 0.0); - opacityFun->AddPoint(-1000, 0, 0.5, 0.0); - opacityFun->AddPoint(-500, 1.0, 0.33, 0.45); - opacityFun->AddPoint(3071, 1.0, 0.5, 0.0); - mapper->SetBlendModeToComposite(); - property->ShadeOn(); - property->SetAmbient(0.1); - property->SetDiffuse(0.9); - property->SetSpecular(0.2); - property->SetSpecularPower(10.0); - property->SetScalarOpacityUnitDistance(0.8919); - break; - case 4: - colorFun->AddRGBPoint(0.0, 0, 0, 1); // Blue - colorFun->AddRGBPoint(level, 0, 1, 0); // Green - colorFun->AddRGBPoint(level + 0.5*window, 1, 1, 0); // Yellow - colorFun->AddRGBPoint(level + window, 1, 0, 0); // Red - opacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0); + case 3: // Rainbow MIP + m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 1); + m_ColorFun->AddRGBPoint(level, 0, 1, 0); + m_ColorFun->AddRGBPoint(level + 0.5 * window, 1, 1, 0); + m_ColorFun->AddRGBPoint(level + window, 1, 0, 0); + m_OpacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0); mapper->SetBlendModeToMaximumIntensity(); break; - case 5: - colorFun->AddRGBSegment(0.0, 1.0, 1.0, 1.0, 255.0, 1.0, 1.0, 1.0); - opacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0); + case 4: // Additive + m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 0); + m_ColorFun->AddRGBPoint(level + 0.5 * window, 1, 1, 1); + m_OpacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0); mapper->SetBlendModeToAdditive(); break; default: - vtkGenericWarningMacro("Unknown blend type."); break; } } +void vtkVoxImage::RescaleShaderRange() { + double range[2]; + m_Image->GetScalarRange(range); + m_Level = (range[0] + range[1]) / 2.0; + m_Window = range[1] - range[0]; + if (m_Window <= 1e-9) + m_Window = 1.0; + setShadingPreset(m_ShadingPreset); +} + void vtkVoxImage::SetRepresentation(Representation mode) { + Puppet::SetRepresentation(mode); // Ensure base class data state is updated + if (mode == Wireframe) { m_Actor->SetVisibility(0); m_OutlineActor->SetVisibility(1); - } else if (mode == Surface) { + m_OutlineActor->GetProperty()->SetRepresentationToWireframe(); + } else if (mode == Volume) { m_Actor->SetVisibility(1); - m_OutlineActor->SetVisibility(1); // Keep outline visible as boundary + m_OutlineActor->SetVisibility(1); + m_OutlineActor->GetProperty()->SetRepresentationToWireframe(); } else { - Puppet::SetRepresentation(mode); + // Other representations (Points, Surface, etc) are handled by basic Puppet + // behavior which affects the m_Asm parts. } } void vtkVoxImage::serialize_display(uLib::Archive::display_properties_archive & ar, const unsigned int version) { - // Call base class if it has display properties + // Call base class to show Transform and Appearance properties Puppet::serialize_display(ar, version); - // Use the member variables if they are available + // Use the member variables for volume rendering parameters ar & boost::serialization::make_hrp("Window", m_Window); ar & boost::serialization::make_hrp("Level", m_Level); - ar & boost::serialization::make_hrp_enum("Shading", m_ShadingPreset, {"MIP", "Composite", "Composite Shaded", "MIP Bone", "MIP Hot", "Additive"}); + ar & boost::serialization::make_hrp_enum("Shading", m_ShadingPreset, + {"MIP", "Composite", "Composite Shaded", "MIP Bone", "MIP Hot", "Additive"}); +} + +void vtkVoxImage::SyncFromVtk() { + if (auto *root = this->GetProxyProp()) { + vtkMatrix4x4 *rootMat = root->GetUserMatrix(); + if (rootMat) { + Matrix4f vtkLocal = VtkToMatrix4f(rootMat); + // Synchronize TRS from VTK, compensating for local volume offset + m_Content.FromMatrix(vtkLocal); // * m_Content.GetLocalMatrix().inverse()); + m_Content.Updated(); + } + } } void vtkVoxImage::Update() { + if (auto *root = vtkProp3D::SafeDownCast(this->GetProp())) { + vtkNew m; + Matrix4fToVtk(m_Content.GetMatrix(), m); // * m_Content.GetLocalMatrix(), m); + root->SetUserMatrix(m); + root->Modified(); + // std::cout << "[vtkVoxImage::Update] Set Proxy UserMatrix:" << std::endl; + // std::cout << m_Content.GetMatrix() << std::endl; + } setShadingPreset(m_ShadingPreset); m_Actor->Update(); m_Outline->SetBounds(m_Image->GetBounds()); m_Outline->Update(); + + ConnectionBlock blocker(m_UpdateConnection); + this->Puppet::Update(); } + void vtkVoxImage::InstallPipe() { vtkSmartPointer mapper = vtkSmartPointer::New(); @@ -320,7 +349,7 @@ void vtkVoxImage::InstallPipe() { mapper->Update(); m_Actor->SetMapper(mapper); - this->setShadingPreset(0); + this->setShadingPreset(m_ShadingPreset); mapper->Update(); m_Outline->SetBounds(m_Image->GetBounds()); @@ -331,13 +360,13 @@ void vtkVoxImage::InstallPipe() { m_OutlineActor->SetMapper(mmapper); m_OutlineActor->GetProperty()->SetRepresentationToWireframe(); m_OutlineActor->GetProperty()->SetAmbient(0.7); - + m_Asm->AddPart(m_Actor); m_Asm->AddPart(m_OutlineActor); this->SetProp(m_Asm); // Default look - this->SetRepresentation(Surface); + this->SetRepresentation(Volume); } } // namespace Vtk diff --git a/src/Vtk/Math/vtkVoxImage.h b/src/Vtk/Math/vtkVoxImage.h index 7b0b087..9f16efa 100644 --- a/src/Vtk/Math/vtkVoxImage.h +++ b/src/Vtk/Math/vtkVoxImage.h @@ -39,6 +39,8 @@ class vtkImageData; class vtkActor; +class vtkColorTransferFunction; +class vtkPiecewiseFunction; namespace uLib { namespace Vtk { @@ -55,6 +57,8 @@ public: void SetContent(); + vtkProp3D *GetProp() override { return m_Asm; } + vtkImageData *GetImageData(); void SaveToXMLFile(const char *fname); @@ -65,8 +69,10 @@ public: void setShadingPreset(int blendType = 2); void SetRepresentation(Representation mode); + void RescaleShaderRange(); void Update() override; + void SyncFromVtk() override; void serialize_display(uLib::Archive::display_properties_archive & ar, const unsigned int version = 0) override; protected: @@ -88,6 +94,11 @@ private: float m_Window; float m_Level; int m_ShadingPreset; + + Connection m_UpdateConnection; + + class vtkColorTransferFunction *m_ColorFun; + class vtkPiecewiseFunction *m_OpacityFun; }; } // namespace Vtk diff --git a/src/Vtk/uLibVtkInterface.cxx b/src/Vtk/uLibVtkInterface.cxx index 1dcbef0..95015c5 100644 --- a/src/Vtk/uLibVtkInterface.cxx +++ b/src/Vtk/uLibVtkInterface.cxx @@ -134,11 +134,11 @@ public: vtkActor *actor = vtkActor::SafeDownCast(p); if (actor) { - if (m_Representation != -1) { + if (m_Representation != -1 && m_Representation != Puppet::Volume) { if (m_Representation == Puppet::SurfaceWithEdges) { actor->GetProperty()->SetRepresentation(VTK_SURFACE); actor->GetProperty()->SetEdgeVisibility(1); - } else { + } else if (m_Representation != Puppet::Outline && m_Representation != Puppet::Slice) { actor->GetProperty()->SetRepresentation(m_Representation); actor->GetProperty()->SetEdgeVisibility(0); } @@ -628,6 +628,8 @@ struct AppearanceProxy { ar & boost::serialization::make_hrp("Visibility", pd->m_Visibility); ar & boost::serialization::make_hrp("Pickable", pd->m_Selectable); ar & boost::serialization::make_hrp("Dragable", pd->m_Dragable); + ar & boost::serialization::make_hrp("ShowBoundingBox", pd->m_ShowBoundingBox); + ar & boost::serialization::make_hrp("ShowScaleMeasures", pd->m_ShowScaleMeasures); } }; diff --git a/src/Vtk/vtkObjectsContext.cpp b/src/Vtk/vtkObjectsContext.cpp index 98daba8..612499c 100644 --- a/src/Vtk/vtkObjectsContext.cpp +++ b/src/Vtk/vtkObjectsContext.cpp @@ -118,18 +118,17 @@ void vtkObjectsContext::SyncFromVtk() { Puppet* vtkObjectsContext::CreatePuppet(uLib::Object* obj) { if (!obj) return nullptr; - if (auto* box = dynamic_cast(obj)) { + if (auto* vox = dynamic_cast(obj)) { + return new vtkVoxImage(*vox); + } else if (auto* box = dynamic_cast(obj)) { return new vtkContainerBox(box); } else if (auto* chamber = dynamic_cast(obj)) { return new vtkDetectorChamber(chamber); } else if (auto* cylinder = dynamic_cast(obj)) { return new vtkCylinder(cylinder); - } else if (auto* vox = dynamic_cast(obj)) { - return new vtkVoxImage(*vox); } else if (auto* assembly = dynamic_cast(obj)) { return new Assembly(assembly); - } - else if (auto* box = dynamic_cast(obj)) { + } else if (auto* box = dynamic_cast(obj)) { return new vtkBoxSolid(box); }