diff --git a/.gitignore b/.gitignore index b676bf1..82547f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ CMakeLists.txt.user m +.DS_Store diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cae699..780ae28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,9 @@ set(SRCS gRen.cpp gLoadPatient.cpp gLocalize.cpp + LocalizationService.cpp + LocalizationWorker.cpp + gSkullRemoval.cpp ) @@ -79,6 +82,11 @@ set(HDR gRen.h gLoadPatient.h gLocalize.h + LocalizationService.h + LocalizationWorker.h + types.h + types_qt.h + gSkullRemoval.h itkQtAdaptor.h ) diff --git a/LocalizationService.cpp b/LocalizationService.cpp new file mode 100644 index 0000000..5e06418 --- /dev/null +++ b/LocalizationService.cpp @@ -0,0 +1,238 @@ +#include "LocalizationService.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace +{ +struct Surf +{ + double length{0.0}; + double hausdorff{0.0}; + double bounds[6]{0, 0, 0, 0, 0, 0}; + double center[3]{0, 0, 0}; + double confronto_lati{0.0}; + int regionId{-1}; + Point3d centroid; +}; + +static Point3d centroidFromPoints(const vtkPoints* pts) +{ + Point3d c; + if (!pts) return c; + const vtkIdType n = pts->GetNumberOfPoints(); + if (n <= 0) return c; + + double sx = 0.0, sy = 0.0, sz = 0.0; + double p[3]; + for (vtkIdType i = 0; i < n; ++i) + { + pts->GetPoint(i, p); + sx += p[0]; + sy += p[1]; + sz += p[2]; + } + c.x = sx / static_cast(n); + c.y = sy / static_cast(n); + c.z = sz / static_cast(n); + return c; +} + +static double sideDifferenceMetric(const double b[6]) +{ + // Legacy "lati" metric: compare extents. + const double dx = std::abs(b[1] - b[0]); + const double dy = std::abs(b[3] - b[2]); + const double dz = std::abs(b[5] - b[4]); + + // difference between the two largest extents (simple, stable) + const double a = std::max({dx, dy, dz}); + const double c = std::min({dx, dy, dz}); + const double mid = dx + dy + dz - a - c; + return std::max({std::abs(a - mid), std::abs(mid - c), std::abs(a - c)}); +} + +static double hausdorffToSphere(vtkPolyData* surface, const double center[3]) +{ + if (!surface || surface->GetNumberOfPoints() == 0) return 0.0; + + vtkNew source_sphere; + source_sphere->SetCenter(center[0], center[1], center[2]); + source_sphere->SetRadius(5.0); + source_sphere->SetThetaResolution(24); + source_sphere->SetPhiResolution(24); + source_sphere->Update(); + + vtkPolyData* sphere = source_sphere->GetOutput(); + if (!sphere || sphere->GetNumberOfPoints() == 0) return 0.0; + + const vtkIdType nSource = surface->GetNumberOfPoints(); + const vtkIdType nTarget = sphere->GetNumberOfPoints(); + + double maxMin = 0.0; + double p1[3], p2[3]; + + for (vtkIdType i = 0; i < nSource; ++i) + { + surface->GetPoint(i, p1); + double smallest = std::numeric_limits::infinity(); + + for (vtkIdType j = 0; j < nTarget; ++j) + { + sphere->GetPoint(j, p2); + const double dx = p1[0] - p2[0]; + const double dy = p1[1] - p2[1]; + const double dz = p1[2] - p2[2]; + const double dist2 = dx * dx + dy * dy + dz * dz; + if (dist2 < smallest) smallest = dist2; + } + + if (smallest > maxMin) maxMin = smallest; + } + + return maxMin; +} + +} // namespace + +MarkerList LocalizationService::localize( + vtkImageData* volume, + const LocalizationParams& params, + const ProgressFn& onProgress, + const AbortFn& shouldAbort) +{ + MarkerList result; + if (!volume) + return result; + + // Marching cubes + vtkNew marchingcubes; + marchingcubes->SetInputData(volume); + marchingcubes->SetValue(0, params.marchThreshold); + marchingcubes->SetComputeNormals(0); + marchingcubes->Update(); + + if (marchingcubes->GetNumberOfContours() <= 0) + return result; + + // Smoothing + vtkNew smoother; + smoother->SetInputConnection(marchingcubes->GetOutputPort()); + smoother->SetNumberOfIterations(20); + smoother->NormalizeCoordinatesOn(); + smoother->SetPassBand(0.01); + smoother->BoundarySmoothingOn(); + smoother->Update(); + + // Normals + vtkNew normals; + normals->SetInputConnection(smoother->GetOutputPort()); + normals->Update(); + + // Connectivity / region labeling + vtkNew connectivity; + connectivity->SetInputConnection(normals->GetOutputPort()); + connectivity->SetExtractionModeToAllRegions(); + connectivity->ColorRegionsOn(); + connectivity->Update(); + + vtkPolyData* labeled = connectivity->GetOutput(); + if (!labeled) + return result; + + const int nRegions = connectivity->GetNumberOfExtractedRegions(); + if (nRegions <= 0) + return result; + + // Threshold each region out of labeled polydata. This is robust across recent VTK versions. + for (int regionId = 0; regionId < nRegions; ++regionId) + { + if (shouldAbort && shouldAbort()) + break; + + if (onProgress) + onProgress(100.0 * static_cast(regionId + 1) / static_cast(nRegions)); + + vtkNew th; + th->SetInputData(labeled); + th->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_CELLS, "RegionId"); + th->SetLowerThreshold(regionId); + th->SetUpperThreshold(regionId); + th->SetThresholdFunction(vtkThreshold::THRESHOLD_BETWEEN); + + vtkNew geom; + geom->SetInputConnection(th->GetOutputPort()); + geom->Update(); + + vtkPolyData* out = geom->GetOutput(); + if (!out || out->GetNumberOfCells() == 0) + continue; + + out->ComputeBounds(); + double b[6]; + out->GetBounds(b); + const double len = out->GetLength(); + + bool ok = true; + + // 0: bounding-box diagonal length + if (params.selectedFilters.size() > 0 && params.selectedFilters[0]) + { + if (len < params.thrDown_D || len > params.thrUp_D) + ok = false; + } + + // 1: Hausdorff + if (ok && params.selectedFilters.size() > 1 && params.selectedFilters[1]) + { + double center[3]; + out->GetCenter(center); + const double hd = hausdorffToSphere(out, center); + if (hd > params.thr_HAUSD) + ok = false; + } + + // 2: side-difference + if (ok && params.selectedFilters.size() > 2 && params.selectedFilters[2]) + { + const double metric = sideDifferenceMetric(b); + if (metric > params.thr_S) + ok = false; + } + + if (!ok) + continue; + + // Centroid from edges, to match legacy behavior. + vtkNew edge_extractor; + edge_extractor->SetInputData(out); + edge_extractor->Update(); + + vtkPolyData* edges = edge_extractor->GetOutput(); + if (!edges || edges->GetNumberOfPoints() == 0) + continue; + + const Point3d c_mm = centroidFromPoints(edges->GetPoints()); + + Marker m; + m.regionId = regionId; + m.centroid = Point3d{c_mm.x / 1000.0, c_mm.y / 1000.0, c_mm.z / 1000.0}; + result.push_back(m); + } + + return result; +} diff --git a/LocalizationService.h b/LocalizationService.h new file mode 100644 index 0000000..ad2fa46 --- /dev/null +++ b/LocalizationService.h @@ -0,0 +1,22 @@ +#pragma once + +#include "types.h" + +#include + +class vtkImageData; + +// Pure logic (no Qt widgets, no rendering). +// Takes a volume and parameters and returns detected marker centroids. +class LocalizationService +{ +public: + using ProgressFn = std::function; + using AbortFn = std::function; + + static MarkerList localize( + vtkImageData* volume, + const LocalizationParams& params, + const ProgressFn& onProgress = {}, + const AbortFn& shouldAbort = {}); +}; diff --git a/LocalizationWorker.cpp b/LocalizationWorker.cpp new file mode 100644 index 0000000..59dece9 --- /dev/null +++ b/LocalizationWorker.cpp @@ -0,0 +1,40 @@ +#include "LocalizationWorker.h" +#include "LocalizationService.h" + +#include + +LocalizationWorker::LocalizationWorker(QObject* parent) + : QObject(parent) +{ +} + +void LocalizationWorker::abort() +{ + m_abort.store(true, std::memory_order_relaxed); +} + +void LocalizationWorker::run(vtkImageData* volume, LocalizationParams params) +{ + m_abort.store(false, std::memory_order_relaxed); + + try { + MarkerList markers = LocalizationService::localize( + volume, + params, + [this](double p) { emit progress(p); }, + [this]() { return m_abort.load(std::memory_order_relaxed); } + ); + + if (m_abort.load(std::memory_order_relaxed)) { + emit aborted(); + return; + } + + emit finished(std::move(markers)); + } catch (const std::exception& e) { + // Keep behavior consistent with legacy code: treat as aborted. + // (UI can be extended to show the exception message.) + (void)e; + emit aborted(); + } +} diff --git a/LocalizationWorker.h b/LocalizationWorker.h new file mode 100644 index 0000000..830f215 --- /dev/null +++ b/LocalizationWorker.h @@ -0,0 +1,29 @@ +#pragma once + +#include "types_qt.h" + +#include + +#include + +class vtkImageData; + +// Qt wrapper around LocalizationService (runs in a worker thread). +class LocalizationWorker : public QObject +{ + Q_OBJECT +public: + explicit LocalizationWorker(QObject* parent = nullptr); + +public slots: + void run(vtkImageData* volume, LocalizationParams params); + void abort(); + +signals: + void progress(double percent); + void finished(MarkerList markers); + void aborted(); + +private: + std::atomic_bool m_abort{false}; +}; diff --git a/gRen.cpp b/gRen.cpp index a09c415..af0926e 100644 --- a/gRen.cpp +++ b/gRen.cpp @@ -1,497 +1,497 @@ - -#include "gRen.h" -#include - - -gRen::gRen(QVTKOpenGLNativeWidget *m_qvtk){ - CTinfos.clear(); - qvtk= m_qvtk; - nmarker=0; - - markerSources.clear(); - markerMappers.clear(); - markerActors.clear(); - return; -} - - -void gRen::init() -{ - - qDebug() << "rw=" << qvtk->renderWindow() - << "iren=" << qvtk->interactor(); - - if(!qvtk || !qvtk->renderWindow()) { - qDebug() << "QVTK widget not ready yet"; - return; - } - - // Interactor must exist before using it - if(!qvtk->renderWindow()->GetInteractor()) { - qvtk->renderWindow()->Render(); // forces initialization in many setups - } - - qDebug()<<"buildRenderPipe"; - buildRenderPipe(); - qDebug()<<"createRenderers"; - createRenderers(); - qDebug()<<"Render"; - qvtk->renderWindow()->Render(); -} - -void gRen::onAutoSkullMaskEnd(vtkImageData* fixedVol){ - cout << "Auto masking complete"<::New(); - //CTvolume->Initialize(); - CTvolume->DeepCopy(fixedVol); - CTvolume->Modified(); - - extractVOI->Update(); - ren1->Render(); - qvtk->renderWindow()->Render(); - - return; -} - - -void gRen::loadVolume(vtkImageData* volCT){ - - CTvolume=vtkSmartPointer::New(); - CTvolume->Initialize(); - CTvolume->DeepCopy(volCT); - CTvolume->Modified(); - CTvolume->GetExtent(CTinfos.extent); - CTvolume->GetSpacing(CTinfos.spacing); - CTvolume->GetOrigin(CTinfos.origin); - cout<< "ORIGIN REN: "<SetInput(CTvolume/*extractVOI->GetOutput()*/); -#else - volumeMapper->SetInputData(CTvolume); -#endif - ren1->AddViewProp(volume); - - if(volume->GetVisibility() == 0) - volume->SetVisibility(false); - else - volume->SetVisibility(true); - - extractVOI->SetInputData(CTvolume); - extractVOI->SetVOI(CTvolume->GetExtent()); - extractVOI->Update(); - - boxCallback_NEW->setNominalGeometry(volCT->GetExtent(),volCT->GetBounds(),volCT->GetSpacing()); - boxWidget_NEW->SetRotationEnabled(false); - boxWidget_NEW->SetScalingEnabled(true); - boxWidget_NEW->SetTranslationEnabled(true); - boxWidget_NEW->SetPriority(1); - boxWidget_NEW->SetInputData((vtkDataSet *) CTvolume); - boxWidget_NEW->PlaceWidget(); - boxWidget_NEW->InsideOutOn(); - boxWidget_NEW->GetOutlineProperty()->SetRepresentationToWireframe(); - boxWidget_NEW->GetOutlineProperty()->SetAmbient(1.0); - boxWidget_NEW->GetOutlineProperty()->SetAmbientColor(1,1,1); - boxWidget_NEW->GetOutlineProperty()->SetLineWidth(3); - boxWidget_NEW->GetSelectedOutlineProperty()->SetRepresentationToWireframe(); - boxWidget_NEW->GetSelectedOutlineProperty()->SetAmbient(1.0); - boxWidget_NEW->GetSelectedOutlineProperty()->SetAmbientColor(1,0,0); - boxWidget_NEW->GetSelectedOutlineProperty()->SetLineWidth(3); - - outline-> SetInputConnection(extractVOI->GetOutputPort()); - - planeWidgetX->SetInputConnection(extractVOI->GetOutputPort()); - planeWidgetY->SetInputConnection(extractVOI->GetOutputPort()); - planeWidgetZ->SetInputConnection(extractVOI->GetOutputPort()); - - if(outlineActor->GetVisibility() == 0){ - } - else{ - planeWidgetX->On(); - planeWidgetY->On(); - planeWidgetZ->On(); - } - - orthoPlanes->SetPlane(0, planeWidgetX); - orthoPlanes->SetPlane(1, planeWidgetY); - orthoPlanes->SetPlane(2, planeWidgetZ); - orthoPlanes->ResetPlanes(); - - vtkCamera * cam = ren1->GetActiveCamera(); - cam->SetPosition(0, 0, -1); - cam->SetFocalPoint(0, 0, 0); - cam->SetViewUp(0, -1, 0); - - ren1->ResetCamera(); - ren1->Render(); - qvtk->renderWindow()->Render(); - - emit PatientLoaded(); - return; - -} - -void gRen::createRenderers(){ - - ren1 = vtkSmartPointer::New(); - ren1->SetBackground(0.13, 0.25, 0.32); - qvtk->renderWindow()->AddRenderer(ren1); - - ren1->AddActor(assi); - ren1->AddActor( outlineActor); - - boxWidget_NEW = vtkSmartPointer::New(); - boxWidget_NEW->SetInteractor(qvtk->interactor()); - boxWidget_NEW->SetPlaceFactor(1.0); - - // - - - - - - - - - - - - - - - - - - - - - - - boxCallback_NEW->rcm=volumeMapper.GetPointer(); - boxWidget_NEW->AddObserver(vtkCommand::InteractionEvent, boxCallback_NEW); - - return; -} - -void gRen::buildRenderPipe(){ - - qDebug()<<"buildRenderPipe: outline"; - - outline = vtkSmartPointer::New(); - - qDebug()<<"buildRenderPipe: outlineMapper"; - - vtkNew outlineMapper; - outlineMapper->SetInputConnection(outline->GetOutputPort()); - - outlineActor = vtkSmartPointer ::New(); - outlineActor->SetMapper(outlineMapper); - - qDebug()<<"buildRenderPipe: picker"; - - vtkNew picker; - picker->SetTolerance(0.005); - - vtkNew ipwProp; - //// //assign default props to the ipw's texture plane actor - - qDebug()<<"buildRenderPipe: planeX"; - - planeWidgetX = vtkSmartPointer::New(); - planeWidgetX->SetInteractor( qvtk->interactor()); - planeWidgetX->SetKeyPressActivationValue('x'); - planeWidgetX->SetPicker(picker); - planeWidgetX->RestrictPlaneToVolumeOn(); - planeWidgetX->GetPlaneProperty()->SetColor(1,0,0); - planeWidgetX->SetTexturePlaneProperty(ipwProp); - planeWidgetX->TextureInterpolateOff(); - planeWidgetX->SetResliceInterpolateToNearestNeighbour(); - planeWidgetX->SetPlaneOrientationToXAxes(); - planeWidgetX->DisplayTextOn(); - planeWidgetX->InteractionOff(); - planeWidgetX->InteractionOn(); - - qDebug()<<"buildRenderPipe: planeY"; - - planeWidgetY = vtkSmartPointer::New(); - planeWidgetY->SetInteractor( qvtk->interactor()); - planeWidgetY->SetKeyPressActivationValue('y'); - planeWidgetY->SetPicker(picker); - planeWidgetY->GetPlaneProperty()->SetColor(1,1,0); - planeWidgetY->SetTexturePlaneProperty(ipwProp); - planeWidgetY->TextureInterpolateOn(); - planeWidgetY->SetResliceInterpolateToLinear(); - planeWidgetY->SetPlaneOrientationToYAxes(); - planeWidgetY->SetLookupTable( planeWidgetX->GetLookupTable()); - planeWidgetY->DisplayTextOn(); - planeWidgetY->UpdatePlacement(); - - qDebug()<<"buildRenderPipe: planeZ"; - - planeWidgetZ = vtkSmartPointer::New(); - planeWidgetZ->SetInteractor( qvtk->interactor()); - planeWidgetZ->SetKeyPressActivationValue('z'); - planeWidgetZ->SetPicker(picker); - planeWidgetZ->GetPlaneProperty()->SetColor(0,0,1); - planeWidgetZ->SetTexturePlaneProperty(ipwProp); - planeWidgetZ->TextureInterpolateOn(); - planeWidgetZ->SetResliceInterpolateToCubic(); - planeWidgetZ->SetPlaneOrientationToZAxes(); - planeWidgetZ->SetLookupTable( planeWidgetX->GetLookupTable()); - planeWidgetZ->DisplayTextOn(); - - qDebug()<<"buildRenderPipe: assi"; - - - assi = vtkSmartPointer::New(); - assi->SetOrigin(0,0,0); - assi->AxisLabelsOff(); - assi->SetTotalLength(15,15,15); - - qDebug()<<"buildRenderPipe: volMapper"; - - // Create mapper (as vtkVolumeMapper smart pointer) - auto gpuMapper = vtkSmartPointer::New(); - gpuMapper->SetBlendModeToComposite(); - if (!gpuMapper) - { - qDebug() << "ERROR: GPU volume mapper creation returned null"; - return; - } - // store into the member (base type) - this->volumeMapper = gpuMapper; - - qDebug()<<"buildRenderPipe: volProps"; - - volumeProperty = vtkSmartPointer::New(); - volumeProperty->ShadeOff(); - volumeProperty->SetInterpolationType(VTK_LINEAR_INTERPOLATION); - - qDebug()<<"buildRenderPipe: opacity"; - - compositeOpacity = vtkSmartPointer::New(); - compositeOpacity->AddPoint(-3024, 0, 0.5, 0.0 ); - compositeOpacity->AddPoint(-16, 0, .49, .61 ); - compositeOpacity->AddPoint(-10, .1, .49, .4 ); - compositeOpacity->AddPoint(500, .4, .49, .4 ); - compositeOpacity->AddPoint(641, .72, .5, 0.0 ); - compositeOpacity->AddPoint(1700, .8, .5, 0.0 ); - compositeOpacity->AddPoint(2500, .8, .5, 0.0 ); - compositeOpacity->AddPoint(3071, .71, 0.5, 0.0); - volumeProperty->SetScalarOpacity(compositeOpacity); // composite first. - - qDebug()<<"buildRenderPipe: color"; - - color = vtkSmartPointer::New(); - color->AddRGBPoint( -3024, 0, 0, 0, 0.5, 0.0 ); - color->AddRGBPoint( -16, 0.73, 0.25, 0.30, 0.49, .61 ); - color->AddRGBPoint( -10, .3, .3, .3, .5, 0.0 ); - color->AddRGBPoint( 500, .7, .7, .7, .5, 0.0 ); - color->AddRGBPoint( 641, .90, .82, .56, .5, 0.0 ); - color->AddRGBPoint( 1700, 1, 0,0, .5, 1 ); - color->AddRGBPoint( 2500, 1, 0,0, .5,.1 ); - color->AddRGBPoint( 3071, 1, 1, 1, .5, 0.0 ); - volumeProperty->SetColor(color); - - volumeProperty->ShadeOn(); - volumeProperty->SetAmbient(0.1); - volumeProperty->SetDiffuse(0.9); - volumeProperty->SetSpecular(0.2); - volumeProperty->SetSpecularPower(10.0); - volumeProperty->SetScalarOpacityUnitDistance(0.8919); - - - qDebug()<<"buildRenderPipe: volume"; - - volume = vtkSmartPointer::New(); - volume->SetMapper(volumeMapper); - volume->SetProperty(volumeProperty); - volume->SetVisibility(false); - - // outlineActor->SetVisibility(true); - // planeWidgetX->EnabledOn(); - // planeWidgetY->EnabledOn(); - // planeWidgetZ->EnabledOn(); - // planeWidgetX->On(); - // planeWidgetY->On(); - // planeWidgetZ->On(); - - qDebug()<<"buildRenderPipe: extractVOI"; - - extractVOI = vtkSmartPointer::New(); - - qDebug()<<"buildRenderPipe: callback"; - - boxCallback_NEW = vtkSmartPointer::New(); - boxCallback_NEW->setExtractVoiFilter(extractVOI); - - qDebug()<<"buildRenderPipe: orthoplanes"; - - orthoPlanes = vtkSmartPointer::New(); - - return; -} - -void gRen::onSingleMarkerChange(int idx, bool state){ - - cout<< "onSingleMarkerChange : "<= static_cast(markerActors.size())) - return; - - auto &act = markerActors.at(static_cast(idx)); - if (!act) - return; - - if(state) - act->GetProperty()->SetColor(0,1,0); - else - act->GetProperty()->SetColor(1,1,1); - qvtk->renderWindow()->Render(); - return; - -} - - -void gRen::renderMarkers(QList marker_list){ - - cout << "renderMarkers" << endl; - - // Remove previous marker actors/labels - for (auto &a : markerActors) - { - if (a) - ren1->RemoveActor(a); - } - markerActors.clear(); - markerMappers.clear(); - markerSources.clear(); - if (pointLabels) - { - ren1->RemoveActor(pointLabels); - pointLabels = nullptr; - } - - nmarker = marker_list.size(); - markerSources.reserve(static_cast(nmarker)); - markerMappers.reserve(static_cast(nmarker)); - markerActors.reserve(static_cast(nmarker)); - - modelPoints = vtkSmartPointer::New(); - modelPoints->Reset(); - modelPoints->SetNumberOfPoints(nmarker); - - - stringData = vtkSmartPointer::New(); - stringData->Reset(); - stringData->SetName("Labels"); - - for(int i=0;iInsertNextValue(QString::number(i).toLatin1().constData()); - modelPoints->InsertPoint(i,marker_list.at(i).centroid.x*1000,marker_list.at(i).centroid.y*1000,marker_list.at(i).centroid.z*1000); - - auto src = vtkSmartPointer::New(); - src->SetCenter(marker_list.at(i).centroid.x*1000,marker_list.at(i).centroid.y*1000,marker_list.at(i).centroid.z*1000); - src->SetRadius(10); - - auto map = vtkSmartPointer::New(); - map->SetInputConnection(src->GetOutputPort()); - - auto act = vtkSmartPointer::New(); - act->SetMapper(map); - act->GetProperty()->SetRepresentationToWireframe(); - act->GetProperty()->SetColor(0,1,0); - ren1->AddActor(act); - - markerSources.push_back(src); - markerMappers.push_back(map); - markerActors.push_back(act); - } - - cout << "modelPoints" << endl; - modelPoints->Modified(); - - cells = vtkSmartPointer::New(); - cells->Reset(); - cells->InsertNextCell(nmarker); - for(int i = 0; i < nmarker; i++) - cells->InsertCellPoint(i); - - polyData = vtkSmartPointer::New(); - polyData->Reset(); - polyData->SetPoints(modelPoints); - polyData->SetVerts(cells); - polyData->GetPointData()->AddArray(stringData); - - polyData->GetFieldData()->AddArray(stringData); - ldm = vtkSmartPointer::New(); - ldm->SetInputData(polyData); - ldm->SetLabelModeToLabelFieldData(); - ldm->SetFieldDataName("Labels"); - ldm->GetLabelTextProperty()->SetShadow(false); - ldm->GetLabelTextProperty()->SetFontSize(22); - cout << "plab" << endl; - pointLabels = vtkSmartPointer::New(); - pointLabels->SetMapper( ldm ); - ren1->AddActor(pointLabels); - qvtk->renderWindow()->Render(); - - return; -} - -void gRen::onReferenceChange(double dx, double dy, double dz){ - - if (markerSources.empty()) - return; - - for(int i=0;i(i)); - const double* c = src->GetCenter(); - src->SetCenter(c[0]-dx, c[1]-dy, c[2]-dz); - src->Update(); - - const double* p = modelPoints->GetPoint(i); - modelPoints->SetPoint(i, p[0]-dx, p[1]-dy, p[2]-dz); - } - - modelPoints->Modified(); - qvtk->renderWindow()->Render(); - return; -} - -void gRen::onRequestRenderChange(int idx){ - switch (idx){ - case 0: - volume->SetVisibility(true); - outlineActor->SetVisibility(false); - planeWidgetX->EnabledOff(); - planeWidgetY->EnabledOff(); - planeWidgetZ->EnabledOff(); - //volumeMapper->SetRequestedRenderMode(2); - qvtk->renderWindow()->Render(); - - // Software mode, for coverage. It also makes sure we will get the same - // regression image on all platforms. - //volumeMapper->SetRequestedRenderModeToRayCast(); - qvtk->renderWindow()->Render(); - - ren1->Render(); - break; - - case 1: - volume->SetVisibility(false); - outlineActor->SetVisibility(true); - planeWidgetX->EnabledOn(); - planeWidgetY->EnabledOn(); - planeWidgetZ->EnabledOn(); - ren1->Render(); - break; - - default: - break; - } - return; -} - - -void gRen::onRequestManualMask(int idx){ - - switch (idx){ - case 0: - boxWidget_NEW->On(); - qvtk->renderWindow()->Render(); - break; - - case 1: - boxWidget_NEW->Off(); - qvtk->renderWindow()->Render(); - break; - - default: - break; - } - return; -} - - - - + +#include "gRen.h" +#include + + +gRen::gRen(QVTKOpenGLNativeWidget *m_qvtk){ + CTinfos.clear(); + qvtk= m_qvtk; + nmarker=0; + + markerSources.clear(); + markerMappers.clear(); + markerActors.clear(); + return; +} + + +void gRen::init() +{ + + qDebug() << "rw=" << qvtk->renderWindow() + << "iren=" << qvtk->interactor(); + + if(!qvtk || !qvtk->renderWindow()) { + qDebug() << "QVTK widget not ready yet"; + return; + } + + // Interactor must exist before using it + if(!qvtk->renderWindow()->GetInteractor()) { + qvtk->renderWindow()->Render(); // forces initialization in many setups + } + + qDebug()<<"buildRenderPipe"; + buildRenderPipe(); + qDebug()<<"createRenderers"; + createRenderers(); + qDebug()<<"Render"; + qvtk->renderWindow()->Render(); +} + +void gRen::onAutoSkullMaskEnd(vtkImageData* fixedVol){ + cout << "Auto masking complete"<::New(); + //CTvolume->Initialize(); + CTvolume->DeepCopy(fixedVol); + CTvolume->Modified(); + + extractVOI->Update(); + ren1->Render(); + qvtk->renderWindow()->Render(); + + return; +} + + +void gRen::loadVolume(vtkImageData* volCT){ + + CTvolume=vtkSmartPointer::New(); + CTvolume->Initialize(); + CTvolume->DeepCopy(volCT); + CTvolume->Modified(); + CTvolume->GetExtent(CTinfos.extent); + CTvolume->GetSpacing(CTinfos.spacing); + CTvolume->GetOrigin(CTinfos.origin); + cout<< "ORIGIN REN: "<SetInput(CTvolume/*extractVOI->GetOutput()*/); +#else + volumeMapper->SetInputData(CTvolume); +#endif + ren1->AddViewProp(volume); + + if(volume->GetVisibility() == 0) + volume->SetVisibility(false); + else + volume->SetVisibility(true); + + extractVOI->SetInputData(CTvolume); + extractVOI->SetVOI(CTvolume->GetExtent()); + extractVOI->Update(); + + boxCallback_NEW->setNominalGeometry(volCT->GetExtent(),volCT->GetBounds(),volCT->GetSpacing()); + boxWidget_NEW->SetRotationEnabled(false); + boxWidget_NEW->SetScalingEnabled(true); + boxWidget_NEW->SetTranslationEnabled(true); + boxWidget_NEW->SetPriority(1); + boxWidget_NEW->SetInputData((vtkDataSet *) CTvolume); + boxWidget_NEW->PlaceWidget(); + boxWidget_NEW->InsideOutOn(); + boxWidget_NEW->GetOutlineProperty()->SetRepresentationToWireframe(); + boxWidget_NEW->GetOutlineProperty()->SetAmbient(1.0); + boxWidget_NEW->GetOutlineProperty()->SetAmbientColor(1,1,1); + boxWidget_NEW->GetOutlineProperty()->SetLineWidth(3); + boxWidget_NEW->GetSelectedOutlineProperty()->SetRepresentationToWireframe(); + boxWidget_NEW->GetSelectedOutlineProperty()->SetAmbient(1.0); + boxWidget_NEW->GetSelectedOutlineProperty()->SetAmbientColor(1,0,0); + boxWidget_NEW->GetSelectedOutlineProperty()->SetLineWidth(3); + + outline-> SetInputConnection(extractVOI->GetOutputPort()); + + planeWidgetX->SetInputConnection(extractVOI->GetOutputPort()); + planeWidgetY->SetInputConnection(extractVOI->GetOutputPort()); + planeWidgetZ->SetInputConnection(extractVOI->GetOutputPort()); + + if(outlineActor->GetVisibility() == 0){ + } + else{ + planeWidgetX->On(); + planeWidgetY->On(); + planeWidgetZ->On(); + } + + orthoPlanes->SetPlane(0, planeWidgetX); + orthoPlanes->SetPlane(1, planeWidgetY); + orthoPlanes->SetPlane(2, planeWidgetZ); + orthoPlanes->ResetPlanes(); + + vtkCamera * cam = ren1->GetActiveCamera(); + cam->SetPosition(0, 0, -1); + cam->SetFocalPoint(0, 0, 0); + cam->SetViewUp(0, -1, 0); + + ren1->ResetCamera(); + ren1->Render(); + qvtk->renderWindow()->Render(); + + emit PatientLoaded(); + return; + +} + +void gRen::createRenderers(){ + + ren1 = vtkSmartPointer::New(); + ren1->SetBackground(0.13, 0.25, 0.32); + qvtk->renderWindow()->AddRenderer(ren1); + + ren1->AddActor(assi); + ren1->AddActor( outlineActor); + + boxWidget_NEW = vtkSmartPointer::New(); + boxWidget_NEW->SetInteractor(qvtk->interactor()); + boxWidget_NEW->SetPlaceFactor(1.0); + + // - - - - - - - - - - - - - - - - - - - - - + + boxCallback_NEW->rcm=volumeMapper.GetPointer(); + boxWidget_NEW->AddObserver(vtkCommand::InteractionEvent, boxCallback_NEW); + + return; +} + +void gRen::buildRenderPipe(){ + + qDebug()<<"buildRenderPipe: outline"; + + outline = vtkSmartPointer::New(); + + qDebug()<<"buildRenderPipe: outlineMapper"; + + vtkNew outlineMapper; + outlineMapper->SetInputConnection(outline->GetOutputPort()); + + outlineActor = vtkSmartPointer ::New(); + outlineActor->SetMapper(outlineMapper); + + qDebug()<<"buildRenderPipe: picker"; + + vtkNew picker; + picker->SetTolerance(0.005); + + vtkNew ipwProp; + //// //assign default props to the ipw's texture plane actor + + qDebug()<<"buildRenderPipe: planeX"; + + planeWidgetX = vtkSmartPointer::New(); + planeWidgetX->SetInteractor( qvtk->interactor()); + planeWidgetX->SetKeyPressActivationValue('x'); + planeWidgetX->SetPicker(picker); + planeWidgetX->RestrictPlaneToVolumeOn(); + planeWidgetX->GetPlaneProperty()->SetColor(1,0,0); + planeWidgetX->SetTexturePlaneProperty(ipwProp); + planeWidgetX->TextureInterpolateOff(); + planeWidgetX->SetResliceInterpolateToNearestNeighbour(); + planeWidgetX->SetPlaneOrientationToXAxes(); + planeWidgetX->DisplayTextOn(); + planeWidgetX->InteractionOff(); + planeWidgetX->InteractionOn(); + + qDebug()<<"buildRenderPipe: planeY"; + + planeWidgetY = vtkSmartPointer::New(); + planeWidgetY->SetInteractor( qvtk->interactor()); + planeWidgetY->SetKeyPressActivationValue('y'); + planeWidgetY->SetPicker(picker); + planeWidgetY->GetPlaneProperty()->SetColor(1,1,0); + planeWidgetY->SetTexturePlaneProperty(ipwProp); + planeWidgetY->TextureInterpolateOn(); + planeWidgetY->SetResliceInterpolateToLinear(); + planeWidgetY->SetPlaneOrientationToYAxes(); + planeWidgetY->SetLookupTable( planeWidgetX->GetLookupTable()); + planeWidgetY->DisplayTextOn(); + planeWidgetY->UpdatePlacement(); + + qDebug()<<"buildRenderPipe: planeZ"; + + planeWidgetZ = vtkSmartPointer::New(); + planeWidgetZ->SetInteractor( qvtk->interactor()); + planeWidgetZ->SetKeyPressActivationValue('z'); + planeWidgetZ->SetPicker(picker); + planeWidgetZ->GetPlaneProperty()->SetColor(0,0,1); + planeWidgetZ->SetTexturePlaneProperty(ipwProp); + planeWidgetZ->TextureInterpolateOn(); + planeWidgetZ->SetResliceInterpolateToCubic(); + planeWidgetZ->SetPlaneOrientationToZAxes(); + planeWidgetZ->SetLookupTable( planeWidgetX->GetLookupTable()); + planeWidgetZ->DisplayTextOn(); + + qDebug()<<"buildRenderPipe: assi"; + + + assi = vtkSmartPointer::New(); + assi->SetOrigin(0,0,0); + assi->AxisLabelsOff(); + assi->SetTotalLength(15,15,15); + + qDebug()<<"buildRenderPipe: volMapper"; + + // Create mapper (as vtkVolumeMapper smart pointer) + auto gpuMapper = vtkSmartPointer::New(); + gpuMapper->SetBlendModeToComposite(); + if (!gpuMapper) + { + qDebug() << "ERROR: GPU volume mapper creation returned null"; + return; + } + // store into the member (base type) + this->volumeMapper = gpuMapper; + + qDebug()<<"buildRenderPipe: volProps"; + + volumeProperty = vtkSmartPointer::New(); + volumeProperty->ShadeOff(); + volumeProperty->SetInterpolationType(VTK_LINEAR_INTERPOLATION); + + qDebug()<<"buildRenderPipe: opacity"; + + compositeOpacity = vtkSmartPointer::New(); + compositeOpacity->AddPoint(-3024, 0, 0.5, 0.0 ); + compositeOpacity->AddPoint(-16, 0, .49, .61 ); + compositeOpacity->AddPoint(-10, .1, .49, .4 ); + compositeOpacity->AddPoint(500, .4, .49, .4 ); + compositeOpacity->AddPoint(641, .72, .5, 0.0 ); + compositeOpacity->AddPoint(1700, .8, .5, 0.0 ); + compositeOpacity->AddPoint(2500, .8, .5, 0.0 ); + compositeOpacity->AddPoint(3071, .71, 0.5, 0.0); + volumeProperty->SetScalarOpacity(compositeOpacity); // composite first. + + qDebug()<<"buildRenderPipe: color"; + + color = vtkSmartPointer::New(); + color->AddRGBPoint( -3024, 0, 0, 0, 0.5, 0.0 ); + color->AddRGBPoint( -16, 0.73, 0.25, 0.30, 0.49, .61 ); + color->AddRGBPoint( -10, .3, .3, .3, .5, 0.0 ); + color->AddRGBPoint( 500, .7, .7, .7, .5, 0.0 ); + color->AddRGBPoint( 641, .90, .82, .56, .5, 0.0 ); + color->AddRGBPoint( 1700, 1, 0,0, .5, 1 ); + color->AddRGBPoint( 2500, 1, 0,0, .5,.1 ); + color->AddRGBPoint( 3071, 1, 1, 1, .5, 0.0 ); + volumeProperty->SetColor(color); + + volumeProperty->ShadeOn(); + volumeProperty->SetAmbient(0.1); + volumeProperty->SetDiffuse(0.9); + volumeProperty->SetSpecular(0.2); + volumeProperty->SetSpecularPower(10.0); + volumeProperty->SetScalarOpacityUnitDistance(0.8919); + + + qDebug()<<"buildRenderPipe: volume"; + + volume = vtkSmartPointer::New(); + volume->SetMapper(volumeMapper); + volume->SetProperty(volumeProperty); + volume->SetVisibility(false); + + // outlineActor->SetVisibility(true); + // planeWidgetX->EnabledOn(); + // planeWidgetY->EnabledOn(); + // planeWidgetZ->EnabledOn(); + // planeWidgetX->On(); + // planeWidgetY->On(); + // planeWidgetZ->On(); + + qDebug()<<"buildRenderPipe: extractVOI"; + + extractVOI = vtkSmartPointer::New(); + + qDebug()<<"buildRenderPipe: callback"; + + boxCallback_NEW = vtkSmartPointer::New(); + boxCallback_NEW->setExtractVoiFilter(extractVOI); + + qDebug()<<"buildRenderPipe: orthoplanes"; + + orthoPlanes = vtkSmartPointer::New(); + + return; +} + +void gRen::onSingleMarkerChange(int idx, bool state){ + + cout<< "onSingleMarkerChange : "<= static_cast(markerActors.size())) + return; + + auto &act = markerActors.at(static_cast(idx)); + if (!act) + return; + + if(state) + act->GetProperty()->SetColor(0,1,0); + else + act->GetProperty()->SetColor(1,1,1); + qvtk->renderWindow()->Render(); + return; + +} + + +void gRen::setMarkers(MarkerList marker_list){ + + cout << "renderMarkers" << endl; + + // Remove previous marker actors/labels + for (auto &a : markerActors) + { + if (a) + ren1->RemoveActor(a); + } + markerActors.clear(); + markerMappers.clear(); + markerSources.clear(); + if (pointLabels) + { + ren1->RemoveActor(pointLabels); + pointLabels = nullptr; + } + + nmarker = marker_list.size(); + markerSources.reserve(static_cast(nmarker)); + markerMappers.reserve(static_cast(nmarker)); + markerActors.reserve(static_cast(nmarker)); + + modelPoints = vtkSmartPointer::New(); + modelPoints->Reset(); + modelPoints->SetNumberOfPoints(nmarker); + + + stringData = vtkSmartPointer::New(); + stringData->Reset(); + stringData->SetName("Labels"); + + for(int i=0;iInsertNextValue(QString::number(i).toLatin1().constData()); + modelPoints->InsertPoint(i,marker_list[i].centroid.x*1000,marker_list[i].centroid.y*1000,marker_list[i].centroid.z*1000); + + auto src = vtkSmartPointer::New(); + src->SetCenter(marker_list[i].centroid.x*1000,marker_list[i].centroid.y*1000,marker_list[i].centroid.z*1000); + src->SetRadius(10); + + auto map = vtkSmartPointer::New(); + map->SetInputConnection(src->GetOutputPort()); + + auto act = vtkSmartPointer::New(); + act->SetMapper(map); + act->GetProperty()->SetRepresentationToWireframe(); + act->GetProperty()->SetColor(0,1,0); + ren1->AddActor(act); + + markerSources.push_back(src); + markerMappers.push_back(map); + markerActors.push_back(act); + } + + cout << "modelPoints" << endl; + modelPoints->Modified(); + + cells = vtkSmartPointer::New(); + cells->Reset(); + cells->InsertNextCell(nmarker); + for(int i = 0; i < nmarker; i++) + cells->InsertCellPoint(i); + + polyData = vtkSmartPointer::New(); + polyData->Reset(); + polyData->SetPoints(modelPoints); + polyData->SetVerts(cells); + polyData->GetPointData()->AddArray(stringData); + + polyData->GetFieldData()->AddArray(stringData); + ldm = vtkSmartPointer::New(); + ldm->SetInputData(polyData); + ldm->SetLabelModeToLabelFieldData(); + ldm->SetFieldDataName("Labels"); + ldm->GetLabelTextProperty()->SetShadow(false); + ldm->GetLabelTextProperty()->SetFontSize(22); + cout << "plab" << endl; + pointLabels = vtkSmartPointer::New(); + pointLabels->SetMapper( ldm ); + ren1->AddActor(pointLabels); + qvtk->renderWindow()->Render(); + + return; +} + +void gRen::onReferenceChange(double dx, double dy, double dz){ + + if (markerSources.empty()) + return; + + for(int i=0;i(i)); + const double* c = src->GetCenter(); + src->SetCenter(c[0]-dx, c[1]-dy, c[2]-dz); + src->Update(); + + const double* p = modelPoints->GetPoint(i); + modelPoints->SetPoint(i, p[0]-dx, p[1]-dy, p[2]-dz); + } + + modelPoints->Modified(); + qvtk->renderWindow()->Render(); + return; +} + +void gRen::onRequestRenderChange(int idx){ + switch (idx){ + case 0: + volume->SetVisibility(true); + outlineActor->SetVisibility(false); + planeWidgetX->EnabledOff(); + planeWidgetY->EnabledOff(); + planeWidgetZ->EnabledOff(); + //volumeMapper->SetRequestedRenderMode(2); + qvtk->renderWindow()->Render(); + + // Software mode, for coverage. It also makes sure we will get the same + // regression image on all platforms. + //volumeMapper->SetRequestedRenderModeToRayCast(); + qvtk->renderWindow()->Render(); + + ren1->Render(); + break; + + case 1: + volume->SetVisibility(false); + outlineActor->SetVisibility(true); + planeWidgetX->EnabledOn(); + planeWidgetY->EnabledOn(); + planeWidgetZ->EnabledOn(); + ren1->Render(); + break; + + default: + break; + } + return; +} + + +void gRen::onRequestManualMask(int idx){ + + switch (idx){ + case 0: + boxWidget_NEW->On(); + qvtk->renderWindow()->Render(); + break; + + case 1: + boxWidget_NEW->Off(); + qvtk->renderWindow()->Render(); + break; + + default: + break; + } + return; +} + + + + diff --git a/gRen.h b/gRen.h index 5e3be03..0dd7e77 100644 --- a/gRen.h +++ b/gRen.h @@ -1,318 +1,318 @@ -#ifndef _GREN_H_ -#define _GREN_H_ - -#include - -#include -#include - -#include -#include -#include -#include "vtkGPUVolumeRayCastMapper.h" -#include -#include - - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include "gLocalize.h" - - -#include - - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - - -#include -#include - - - - -class vtkMyCallback : public vtkCommand -{ -public: - vtkAbstractVolumeMapper *rcm; - static vtkMyCallback *New() { return new vtkMyCallback; } - - virtual void Execute(vtkObject *caller, unsigned long, void*) - { - vtkBoxWidget *boxWidget = reinterpret_cast(caller); - - // Get the actual box coordinates/planes - vtkSmartPointer polydata = vtkSmartPointer::New(); - //static_cast(boxWidget->GetRepresentation())->GetPolyData (polydata); - - boxWidget->GetPolyData(polydata); - double *ac_bounds=polydata->GetBounds(); - /*check if box is larger than volume size*/ - // double *ac_bounds= boxWidget->GetRepresentation()->GetBounds(); - - for(int ii =0 ;ii<6 ;ii+=2 ){ - if(ac_bounds[ii]nominalBounds[ii]) - ac_bounds[ii]=nominalBounds[ii]; - } - - boxWidget->PlaceWidget(ac_bounds); - // boxWidget->Render(); - - /*clip the orthoplanes*/ - extractVOI->SetVOI( nominalExtent[0] + (int) (abs(ac_bounds[0]-nominalBounds[0])/VolSpacing[0]), - nominalExtent[1] - (int) (abs(ac_bounds[1]-nominalBounds[1])/VolSpacing[0]), - nominalExtent[2] +(int) (abs(ac_bounds[2]-nominalBounds[2])/VolSpacing[1]), - nominalExtent[3] -(int) (abs(ac_bounds[3]-nominalBounds[3])/VolSpacing[1]), - nominalExtent[4] + (int) (abs(ac_bounds[4]-nominalBounds[4])/VolSpacing[2]), - nominalExtent[5] -(int) (abs(ac_bounds[5]-nominalBounds[5])/VolSpacing[2]) - ); - - - /* clip the vol ren */ - // Keep the vtkPlanes object alive for as long as the callback lives. - if (!this->Planes) - { - this->Planes = vtkSmartPointer::New(); - } - // The implicit function vtkPlanes is used in conjunction with the - // volume ray cast mapper to limit which portion of the volume is - // volume rendered. - boxWidget->GetPlanes(this->Planes); - this->rcm->SetClippingPlanes(this->Planes); - } - - void setNominalGeometry(int* extents, double* bounds, double* spacing){ - memcpy(&nominalBounds,bounds,6*sizeof(double)); - memcpy(&nominalExtent,extents,6*sizeof(int)); - memcpy(&VolSpacing,spacing,3*sizeof(double)); - } - - void setExtractVoiFilter(vtkExtractVOI* p_extractVOI){ - extractVOI=p_extractVOI; - }; - -private: - vtkExtractVOI* extractVOI; - double nominalBounds[6]; - int nominalExtent[6]; - double VolSpacing[3]; - vtkSmartPointer Planes; -}; - - -// This does the actual work. -// Callback for the interaction -class vtkBoxCallback : public vtkCommand -{ -public: - static vtkBoxCallback *New() - { - return new vtkBoxCallback; - } - - void setExtractVoiFilter(vtkExtractVOI* p_extractVOI){ - extractVOI=p_extractVOI; - }; - - void setVolumeMapper(vtkSmartVolumeMapper* p_mapper){ - volMapper=p_mapper; - } - - void setRenderer(vtkRenderer* p_ren){ - ren=p_ren; - } - - void setNominalGeometry(int* extents, double* bounds, double* spacing){ - memcpy(&nominalBounds,bounds,6*sizeof(double)); - memcpy(&nominalExtent,extents,6*sizeof(int)); - memcpy(&VolSpacing,spacing,3*sizeof(double)); - } - - virtual void Execute(vtkObject *caller, unsigned long, void*) - { - - vtkBoxWidget2 *boxWidget = reinterpret_cast(caller); - - // Get the actual box coordinates/planes - vtkSmartPointer polydata = vtkSmartPointer::New(); - static_cast(boxWidget->GetRepresentation())->GetPolyData (polydata); - - /*check if box is larger than volume size*/ - double *ac_bounds= boxWidget->GetRepresentation()->GetBounds(); - - for(int ii =0 ;ii<6 ;ii+=2 ){ - if(ac_bounds[ii]nominalBounds[ii]) - ac_bounds[ii]=nominalBounds[ii]; - } - - boxWidget->GetRepresentation()->PlaceWidget(ac_bounds); - boxWidget->Render(); - - // Display the center of the box - // double p[3]; - // polydata->GetPoint(14,p); // As per the vtkBoxRepresentation documentation, the 15th point (index 14) is the center of the box - //std::cout << "Box center: " << p[0] << " " << p[1] << " " << p[2] << std::endl; - - //cout<<"nominal VOI"<GetVOI()[0]<<" "<GetVOI()[1]<<" "<GetVOI()[2]<<" "<< - // extractVOI->GetVOI()[3]<<" "<GetVOI()[4]<<" "<GetVOI()[5]<SetVOI( nominalExtent[0] + (int) (abs(ac_bounds[0]-nominalBounds[0])/VolSpacing[0]), - nominalExtent[1] - (int) (abs(ac_bounds[1]-nominalBounds[1])/VolSpacing[0]), - nominalExtent[2] +(int) (abs(ac_bounds[2]-nominalBounds[2])/VolSpacing[1]), - nominalExtent[3] -(int) (abs(ac_bounds[3]-nominalBounds[3])/VolSpacing[1]), - nominalExtent[4] + (int) (abs(ac_bounds[4]-nominalBounds[4])/VolSpacing[2]), - nominalExtent[5] -(int) (abs(ac_bounds[5]-nominalBounds[5])/VolSpacing[2]) - ); - - //extractVOI->Update(); - //volMapper->Update(); - ren->Render(); - - } - vtkBoxCallback(){} - -private: - vtkExtractVOI* extractVOI; - double nominalBounds[6]; - int nominalExtent[6]; - double VolSpacing[3]; - vtkRenderer* ren; - vtkSmartVolumeMapper* volMapper; - -}; - -class gRen : public QObject{ - Q_OBJECT - -public: - gRen( QVTKOpenGLNativeWidget* m_qvtk); - void init(); - - vtkImageData* getVolume(){return extractVOI->GetOutput();} - double* getSpacing(){return CTvolume->GetSpacing();} - -private: - vtkSmartPointer CTvolume; - vtkSmartPointer outline; - vtkSmartPointer outlineActor; - vtkSmartPointer colorMap_X; - vtkSmartPointer planeWidgetX; - vtkSmartPointer planeWidgetY; - vtkSmartPointer planeWidgetZ; - QVTKOpenGLNativeWidget* qvtk; - // vtkSmartPointer myrenderWindow; - vtkSmartPointer orthoPlanes; - vtkSmartPointer assi; - vtkSmartPointer ren1; - - // NOTE: vtkGPUVolumeRayCastMapper is used at runtime, but we keep a - // concrete smart pointer for stability and to avoid nullptr issues. - vtkSmartPointer volumeMapper; - - vtkSmartPointer color; - vtkSmartPointer compositeOpacity; - vtkSmartPointer volume; - vtkSmartPointer volumeProperty; - vtkSmartPointer boxWidget; - vtkSmartPointer boxRepresentation; - vtkSmartPointer boxCallback ; - vtkSmartPointer boxCallback_NEW; - vtkSmartPointer extractVOI; - vtkSmartPointer boxWidget_NEW; - - std::vector> markerSources; - std::vector> markerMappers; - std::vector> markerActors; - vtkSmartPointer modelPoints; - vtkSmartPointer pointLabels; - int nmarker; - vtkSmartPointer stringData; - vtkSmartPointer cells ; - vtkSmartPointer polyData; - // vtkSmartPointer ids; - vtkSmartPointer ldm; - - struct{ - int extent [6]; - double center [3]; - double origin [3]; - double spacing [3]; - QString fName; - void clear(){ - std::fill(extent,extent+6,9999); - std::fill(center,center+3,9999); - std::fill(origin,origin+3,9999); - std::fill(spacing,spacing+3,9999); - fName.clear(); - }; - }CTinfos; - - void buildRenderPipe(); - void createRenderers(); - - -public slots: - void loadVolume (vtkImageData* volCT); - void renderMarkers(QList marker_list); - void onRequestRenderChange(int idx); - void onRequestManualMask(int idx); - void onAutoSkullMaskEnd(vtkImageData* fixedVol); - void onSingleMarkerChange(int idx,bool state); - void onReferenceChange(double dx, double dy, double dz); - - - -signals: - void PatientLoaded(); - -}; - -#endif +#ifndef _GREN_H_ +#define _GREN_H_ + +#include + +#include +#include + +#include +#include +#include +#include "vtkGPUVolumeRayCastMapper.h" +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include "types_qt.h" + + +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + + +#include +#include + + + + +class vtkMyCallback : public vtkCommand +{ +public: + vtkAbstractVolumeMapper *rcm; + static vtkMyCallback *New() { return new vtkMyCallback; } + + virtual void Execute(vtkObject *caller, unsigned long, void*) + { + vtkBoxWidget *boxWidget = reinterpret_cast(caller); + + // Get the actual box coordinates/planes + vtkSmartPointer polydata = vtkSmartPointer::New(); + //static_cast(boxWidget->GetRepresentation())->GetPolyData (polydata); + + boxWidget->GetPolyData(polydata); + double *ac_bounds=polydata->GetBounds(); + /*check if box is larger than volume size*/ + // double *ac_bounds= boxWidget->GetRepresentation()->GetBounds(); + + for(int ii =0 ;ii<6 ;ii+=2 ){ + if(ac_bounds[ii]nominalBounds[ii]) + ac_bounds[ii]=nominalBounds[ii]; + } + + boxWidget->PlaceWidget(ac_bounds); + // boxWidget->Render(); + + /*clip the orthoplanes*/ + extractVOI->SetVOI( nominalExtent[0] + (int) (abs(ac_bounds[0]-nominalBounds[0])/VolSpacing[0]), + nominalExtent[1] - (int) (abs(ac_bounds[1]-nominalBounds[1])/VolSpacing[0]), + nominalExtent[2] +(int) (abs(ac_bounds[2]-nominalBounds[2])/VolSpacing[1]), + nominalExtent[3] -(int) (abs(ac_bounds[3]-nominalBounds[3])/VolSpacing[1]), + nominalExtent[4] + (int) (abs(ac_bounds[4]-nominalBounds[4])/VolSpacing[2]), + nominalExtent[5] -(int) (abs(ac_bounds[5]-nominalBounds[5])/VolSpacing[2]) + ); + + + /* clip the vol ren */ + // Keep the vtkPlanes object alive for as long as the callback lives. + if (!this->Planes) + { + this->Planes = vtkSmartPointer::New(); + } + // The implicit function vtkPlanes is used in conjunction with the + // volume ray cast mapper to limit which portion of the volume is + // volume rendered. + boxWidget->GetPlanes(this->Planes); + this->rcm->SetClippingPlanes(this->Planes); + } + + void setNominalGeometry(int* extents, double* bounds, double* spacing){ + memcpy(&nominalBounds,bounds,6*sizeof(double)); + memcpy(&nominalExtent,extents,6*sizeof(int)); + memcpy(&VolSpacing,spacing,3*sizeof(double)); + } + + void setExtractVoiFilter(vtkExtractVOI* p_extractVOI){ + extractVOI=p_extractVOI; + }; + +private: + vtkExtractVOI* extractVOI; + double nominalBounds[6]; + int nominalExtent[6]; + double VolSpacing[3]; + vtkSmartPointer Planes; +}; + + +// This does the actual work. +// Callback for the interaction +class vtkBoxCallback : public vtkCommand +{ +public: + static vtkBoxCallback *New() + { + return new vtkBoxCallback; + } + + void setExtractVoiFilter(vtkExtractVOI* p_extractVOI){ + extractVOI=p_extractVOI; + }; + + void setVolumeMapper(vtkSmartVolumeMapper* p_mapper){ + volMapper=p_mapper; + } + + void setRenderer(vtkRenderer* p_ren){ + ren=p_ren; + } + + void setNominalGeometry(int* extents, double* bounds, double* spacing){ + memcpy(&nominalBounds,bounds,6*sizeof(double)); + memcpy(&nominalExtent,extents,6*sizeof(int)); + memcpy(&VolSpacing,spacing,3*sizeof(double)); + } + + virtual void Execute(vtkObject *caller, unsigned long, void*) + { + + vtkBoxWidget2 *boxWidget = reinterpret_cast(caller); + + // Get the actual box coordinates/planes + vtkSmartPointer polydata = vtkSmartPointer::New(); + static_cast(boxWidget->GetRepresentation())->GetPolyData (polydata); + + /*check if box is larger than volume size*/ + double *ac_bounds= boxWidget->GetRepresentation()->GetBounds(); + + for(int ii =0 ;ii<6 ;ii+=2 ){ + if(ac_bounds[ii]nominalBounds[ii]) + ac_bounds[ii]=nominalBounds[ii]; + } + + boxWidget->GetRepresentation()->PlaceWidget(ac_bounds); + boxWidget->Render(); + + // Display the center of the box + // double p[3]; + // polydata->GetPoint(14,p); // As per the vtkBoxRepresentation documentation, the 15th point (index 14) is the center of the box + //std::cout << "Box center: " << p[0] << " " << p[1] << " " << p[2] << std::endl; + + //cout<<"nominal VOI"<GetVOI()[0]<<" "<GetVOI()[1]<<" "<GetVOI()[2]<<" "<< + // extractVOI->GetVOI()[3]<<" "<GetVOI()[4]<<" "<GetVOI()[5]<SetVOI( nominalExtent[0] + (int) (abs(ac_bounds[0]-nominalBounds[0])/VolSpacing[0]), + nominalExtent[1] - (int) (abs(ac_bounds[1]-nominalBounds[1])/VolSpacing[0]), + nominalExtent[2] +(int) (abs(ac_bounds[2]-nominalBounds[2])/VolSpacing[1]), + nominalExtent[3] -(int) (abs(ac_bounds[3]-nominalBounds[3])/VolSpacing[1]), + nominalExtent[4] + (int) (abs(ac_bounds[4]-nominalBounds[4])/VolSpacing[2]), + nominalExtent[5] -(int) (abs(ac_bounds[5]-nominalBounds[5])/VolSpacing[2]) + ); + + //extractVOI->Update(); + //volMapper->Update(); + ren->Render(); + + } + vtkBoxCallback(){} + +private: + vtkExtractVOI* extractVOI; + double nominalBounds[6]; + int nominalExtent[6]; + double VolSpacing[3]; + vtkRenderer* ren; + vtkSmartVolumeMapper* volMapper; + +}; + +class gRen : public QObject{ + Q_OBJECT + +public: + gRen( QVTKOpenGLNativeWidget* m_qvtk); + void init(); + + vtkImageData* getVolume(){return extractVOI->GetOutput();} + double* getSpacing(){return CTvolume->GetSpacing();} + +private: + vtkSmartPointer CTvolume; + vtkSmartPointer outline; + vtkSmartPointer outlineActor; + vtkSmartPointer colorMap_X; + vtkSmartPointer planeWidgetX; + vtkSmartPointer planeWidgetY; + vtkSmartPointer planeWidgetZ; + QVTKOpenGLNativeWidget* qvtk; + // vtkSmartPointer myrenderWindow; + vtkSmartPointer orthoPlanes; + vtkSmartPointer assi; + vtkSmartPointer ren1; + + // NOTE: vtkGPUVolumeRayCastMapper is used at runtime, but we keep a + // concrete smart pointer for stability and to avoid nullptr issues. + vtkSmartPointer volumeMapper; + + vtkSmartPointer color; + vtkSmartPointer compositeOpacity; + vtkSmartPointer volume; + vtkSmartPointer volumeProperty; + vtkSmartPointer boxWidget; + vtkSmartPointer boxRepresentation; + vtkSmartPointer boxCallback ; + vtkSmartPointer boxCallback_NEW; + vtkSmartPointer extractVOI; + vtkSmartPointer boxWidget_NEW; + + std::vector> markerSources; + std::vector> markerMappers; + std::vector> markerActors; + vtkSmartPointer modelPoints; + vtkSmartPointer pointLabels; + int nmarker; + vtkSmartPointer stringData; + vtkSmartPointer cells ; + vtkSmartPointer polyData; + // vtkSmartPointer ids; + vtkSmartPointer ldm; + + struct{ + int extent [6]; + double center [3]; + double origin [3]; + double spacing [3]; + QString fName; + void clear(){ + std::fill(extent,extent+6,9999); + std::fill(center,center+3,9999); + std::fill(origin,origin+3,9999); + std::fill(spacing,spacing+3,9999); + fName.clear(); + }; + }CTinfos; + + void buildRenderPipe(); + void createRenderers(); + + +public slots: + void loadVolume (vtkImageData* volCT); + void setMarkers(MarkerList marker_list); + void onRequestRenderChange(int idx); + void onRequestManualMask(int idx); + void onAutoSkullMaskEnd(vtkImageData* fixedVol); + void onSingleMarkerChange(int idx,bool state); + void onReferenceChange(double dx, double dy, double dz); + + + +signals: + void PatientLoaded(); + +}; + +#endif diff --git a/main.cpp b/main.cpp index d9a725e..c223ff1 100644 --- a/main.cpp +++ b/main.cpp @@ -1,6 +1,7 @@ -#include "mainw.h" +#include "mainw.h" +#include "types_qt.h" #include #include "itkTextOutput.h" @@ -19,6 +20,10 @@ int main(int argc, char **argv) { QSurfaceFormat::setDefaultFormat(QVTKOpenGLNativeWidget::defaultFormat()); QApplication app(argc,argv); + qRegisterMetaType("MarkerList"); + qRegisterMetaType("Marker"); + qRegisterMetaType("LocalizationParams"); + if(!QFile("config.ini").exists()){ QMessageBox msgBox; diff --git a/mainw.cpp b/mainw.cpp index ab6e8a7..69c7b7c 100644 --- a/mainw.cpp +++ b/mainw.cpp @@ -1,6 +1,8 @@ #include "mainw.h" #include + +#include #include #include @@ -480,7 +482,6 @@ MainWindow::MainWindow(QString p_loadPath){ qRegisterMetaType< std::vector >("std::vector"); qRegisterMetaType< std::string >("std::string"); qRegisterMetaType< vtkImageData* >("vtkImageData*"); - qRegisterMetaType < QList > ("QList"); qRegisterMetaType < QList > ("QList"); connect(loadPatient, SIGNAL(loadEnd(vtkImageData*)), Visualizer,SLOT(loadVolume (vtkImageData* ))); @@ -491,12 +492,15 @@ MainWindow::MainWindow(QString p_loadPath){ connect(loadPatient, SIGNAL(virtualIsoTested(bool)),this,SLOT(onVirtualIsoTested(bool))); - localize_thread = new QThread; - localize_marker = new gLocalize; - localize_marker->moveToThread(localize_thread); - localize_thread->start(); - connect(localize_marker,SIGNAL(localizationEnd(QList )),this,SLOT(onLocalizationEnd(QList ))); - connect(localize_marker,SIGNAL(localizationProgress(int)), this->Ui->progBar ,SLOT(setValue(int))); + localize_thread = new QThread; + localize_marker = new LocalizationWorker; + localize_marker->moveToThread(localize_thread); + localize_thread->start(); + + connect(localize_marker, SIGNAL(finished(MarkerList)), this, SLOT(onLocalizationEnd(MarkerList))); + connect(localize_marker, SIGNAL(progress(double)), this, SLOT(onLocalizationProgress(double))); + connect(localize_marker, SIGNAL(aborted()), this, SLOT(slot_localize_aborted())); + connect(localize_marker, SIGNAL(failed(QString)), this, SLOT(onLocalizationError(QString))); skullRemoval= new gSkullRemoval; skull_thread= new QThread; @@ -698,24 +702,23 @@ void MainWindow::onLocalizationAborted(){ return; } -void MainWindow::call_localize_start(){ - - //bool selected[3]; - QList selected; - selected.clear(); - selected.append(Ui->checkBox_2->isChecked()); - selected.append(Ui->checkBox_3->isChecked()); - selected.append(Ui->checkBox_5->isChecked()); - - - QMetaObject::invokeMethod(localize_marker, "localize", Qt::QueuedConnection, - Q_ARG(vtkImageData*, Visualizer->getVolume()), - Q_ARG(int,Ui->spinBox->text().toInt()), - Q_ARG(double,Ui->doubleSpinBox_2->text().toDouble()), - Q_ARG(double,Ui->doubleSpinBox_2bis->text().toDouble()), - Q_ARG(double,Ui->doubleSpinBox_3->text().toDouble()), - Q_ARG(double,Ui->doubleSpinBox_5->text().toDouble()), - Q_ARG(QList ,selected)); +void MainWindow::call_localize_start(){ + + LocalizationParams params; + params.marchThreshold = Ui->spinBox->text().toInt(); + params.thrDown_D = Ui->doubleSpinBox_2->text().toDouble(); + params.thrUp_D = Ui->doubleSpinBox_2bis->text().toDouble(); + params.thr_HAUSD = Ui->doubleSpinBox_3->text().toDouble(); + params.thr_S = Ui->doubleSpinBox_5->text().toDouble(); + params.selectedFilters = { + Ui->checkBox_2->isChecked(), + Ui->checkBox_3->isChecked(), + Ui->checkBox_5->isChecked() + }; + + QMetaObject::invokeMethod(localize_marker, "run", Qt::QueuedConnection, + Q_ARG(vtkImageData*, Visualizer->getVolume()), + Q_ARG(LocalizationParams, params)); QMetaObject::invokeMethod(Ui->statusbar, "showMessage", Qt::QueuedConnection, @@ -728,7 +731,7 @@ void MainWindow::call_localize_start(){ void MainWindow::call_localize_cancel(){ - QMetaObject::invokeMethod(localize_marker, "callAbort", Qt::QueuedConnection); + QMetaObject::invokeMethod(localize_marker, "abort", Qt::QueuedConnection); Ui->pushButton_4->setEnabled(false); return;} @@ -758,7 +761,7 @@ void MainWindow::updateMarkerPos(double dx, double dy, double dz){ return; } -void MainWindow::onLocalizationEnd(QList marker_list){ +void MainWindow::onLocalizationEnd(MarkerList marker_list){ Ui->treeView->reset(); treeModel->clear(); @@ -766,11 +769,9 @@ void MainWindow::onLocalizationEnd(QList marker_list){ Ui->pushButton_4->setEnabled(false); Ui->pushButton_3->setEnabled(true); - if(marker_list.size() == 0){ - Ui->pb_save->setEnabled(false); - QList marker_list; - QMetaObject::invokeMethod(Visualizer, "renderMarkers", Qt::QueuedConnection, - Q_ARG(QList,marker_list)); + if(marker_list.empty()){ + Ui->pb_save->setEnabled(false); + Visualizer->setMarkers(MarkerList{}); QMetaObject::invokeMethod(Ui->statusbar, "showMessage", Qt::QueuedConnection, Q_ARG(QString, "Localization done. No markers found." ), @@ -793,18 +794,18 @@ void MainWindow::onLocalizationEnd(QList marker_list){ QList columnList; columnList.clear(); - columnList.append(new QStandardItem(QString("%0").arg(marker_list.at(i).centroid.x*1000))); - columnList.append(new QStandardItem(QString("%0").arg(marker_list.at(i).centroid.y*1000))); - columnList.append(new QStandardItem(QString("%0").arg(marker_list.at(i).centroid.z*1000))); + columnList.append(new QStandardItem(QString("%0").arg(marker_list[i].centroid.x*1000))); + columnList.append(new QStandardItem(QString("%0").arg(marker_list[i].centroid.y*1000))); + columnList.append(new QStandardItem(QString("%0").arg(marker_list[i].centroid.z*1000))); item->appendRows(columnList); - // cout<< "Scritto tree: " <appendRow(item); treeModel->setItem(i, 0, item); } Ui->treeView->setModel (treeModel); - Visualizer->renderMarkers(marker_list); + Visualizer->setMarkers(marker_list); Ui->statusbar->showMessage("Localization done.",3); QMetaObject::invokeMethod(Ui->statusbar, "showMessage", Qt::QueuedConnection, @@ -1051,3 +1052,17 @@ void MainWindow::onManualMaskTrigger(bool state){ } + +void MainWindow::onLocalizationProgress(double percent) +{ + // Keep UI updates on the GUI thread + Ui->progBar->setValue(static_cast(percent)); +} + +void MainWindow::onLocalizationError(const QString& message) +{ + qWarning() << "Localization error:" << message; + // Re-enable UI bits if needed + Ui->pb_localize->setEnabled(true); + Ui->pb_localize_abort->setEnabled(false); +} diff --git a/mainw.h b/mainw.h index 19dfce2..b62edb0 100644 --- a/mainw.h +++ b/mainw.h @@ -48,7 +48,8 @@ #include #include "gLoadPatient.h" -#include "gLocalize.h" +#include "LocalizationWorker.h" +#include "types_qt.h" #include #include @@ -175,7 +176,7 @@ private: gLoadPatient* loadPatient; QThread* loadPatient_thread; - gLocalize* localize_marker; + LocalizationWorker* localize_marker{nullptr}; QThread* localize_thread; gSkullRemoval* skullRemoval; QThread* skull_thread; @@ -205,7 +206,9 @@ private slots: void onParsedOK(int result, gPatientRTGeneralInfos* data); void onParsedEmptyFolder(); - void onLocalizationEnd(QList marker_list); + void onLocalizationEnd(MarkerList marker_list); + void onLocalizationProgress(double percent); + void onLocalizationError(const QString& message); void onLocalizationAborted(); void onVisualizationTrigger(QAction* trigger_action); void onManualMaskTrigger(bool state); diff --git a/types.h b/types.h new file mode 100644 index 0000000..2c29505 --- /dev/null +++ b/types.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +// Shared data model used across logic, Qt UI, and VTK rendering. +// Keep this header free of Qt Widgets / VTK includes. + +struct Point3d +{ + double x{0.0}; + double y{0.0}; + double z{0.0}; +}; + +struct Marker +{ + int regionId{-1}; + // In meters (legacy code converts from mm by dividing by 1000). + Point3d centroid; +}; + +using MarkerList = std::vector; + +struct LocalizationParams +{ + int marchThreshold{3000}; + double thrDown_D{0.0}; + double thrUp_D{0.0}; + double thr_HAUSD{0.0}; + double thr_S{0.0}; + + // Index mapping matches the legacy code: + // 0: bounding-box diagonal length filter + // 1: Hausdorff distance filter + // 2: side-difference filter + std::vector selectedFilters{true, true, true}; +}; diff --git a/types_qt.h b/types_qt.h new file mode 100644 index 0000000..4aa2ac4 --- /dev/null +++ b/types_qt.h @@ -0,0 +1,9 @@ +#pragma once + +#include "types.h" + +#include + +Q_DECLARE_METATYPE(Marker) +Q_DECLARE_METATYPE(MarkerList) +Q_DECLARE_METATYPE(LocalizationParams)