Skip to content

Commit 77349d9

Browse files
authored
Merge pull request #115 from dyollb/feature/topology_fix_manifold
Feature/topology fix manifold
2 parents 225f50e + 3502380 commit 77349d9

File tree

6 files changed

+89
-20
lines changed

6 files changed

+89
-20
lines changed

Core/Graph.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <array>
99
#include <cmath>
10+
#include <type_traits>
1011

1112
namespace XCore
1213
{
@@ -50,7 +51,7 @@ namespace XCore
5051

5152
public:
5253
using idx_type = TIndex;
53-
using idx_value = typename idx_type::IndexValueType;
54+
using idx_value = typename std::decay<decltype(std::declval<idx_type&>()[0])>::type;
5455
using idx_list_type = StackVector<idx_type, Pow(3, D) - 1>;
5556

5657
CUniformGridGraph(const std::array<idx_value, D>& dims, const std::array<double, D>& spacing)

Core/TopologyInvariants.h

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ bool EulerInvariant(const TNeighborhood& neighbors, const TLabel label)
3030
// ==> If we mark the center as BG, then P, F, E and V are '0'.
3131
static const int c = 27 / 2;
3232

33-
//fprintf(stderr, "neighbors[c]: %d vs %d", static_cast<int>(neighbors[c]), static_cast<int>(label));
3433
assert(neighbors[c] == label);
3534

3635
// Voxels (parallelepipeds) - count center
@@ -211,4 +210,28 @@ bool CCInvariant(TNeighborhood neighbors, const TLabel label)
211210
return (cc_before == cc_after);
212211
}
213212

213+
/** \brief Will voxels be manifold after removing center voxel?
214+
*/
215+
template<typename TNeighborhood, typename TLabel>
216+
bool NonmanifoldRemove(const TNeighborhood& neighbors, const TLabel label)
217+
{
218+
static const int c = 27 / 2;
219+
220+
// test for non-manifold edges around center voxel
221+
return (neighbors[c-1]==label && neighbors[c-3]==label && neighbors[c-1-3]!=label)
222+
|| (neighbors[c+1]==label && neighbors[c-3]==label && neighbors[c+1-3]!=label)
223+
|| (neighbors[c-1]==label && neighbors[c+3]==label && neighbors[c-1+3]!=label)
224+
|| (neighbors[c+1]==label && neighbors[c+3]==label && neighbors[c+1+3]!=label)
225+
//
226+
|| (neighbors[c-1]==label && neighbors[c-9]==label && neighbors[c-1-9]!=label)
227+
|| (neighbors[c+1]==label && neighbors[c-9]==label && neighbors[c+1-9]!=label)
228+
|| (neighbors[c-1]==label && neighbors[c+9]==label && neighbors[c-1+9]!=label)
229+
|| (neighbors[c+1]==label && neighbors[c+9]==label && neighbors[c+1+9]!=label)
230+
//
231+
|| (neighbors[c-3]==label && neighbors[c-9]==label && neighbors[c-3-9]!=label)
232+
|| (neighbors[c+3]==label && neighbors[c-9]==label && neighbors[c+3-9]!=label)
233+
|| (neighbors[c-3]==label && neighbors[c+9]==label && neighbors[c-3+9]!=label)
234+
|| (neighbors[c+3]==label && neighbors[c+9]==label && neighbors[c+3+9]!=label);
235+
}
236+
214237
} // namespace topology

Core/itkFixTopologyCarveOutside.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ class /*ITK_TEMPLATE_EXPORT*/ FixTopologyCarveOutside : public ImageToImageFilte
8181
itkSetMacro(OutsideValue, InputImagePixelType);
8282
itkGetConstMacro(OutsideValue, InputImagePixelType);
8383

84+
itkSetMacro(EnforceManifold, bool);
85+
itkGetConstMacro(EnforceManifold, bool);
86+
8487
/** Get Skeleton by thinning image. */
8588
OutputImageType* GetThinning();
8689

@@ -116,6 +119,7 @@ class /*ITK_TEMPLATE_EXPORT*/ FixTopologyCarveOutside : public ImageToImageFilte
116119
private:
117120
InputImagePixelType m_InsideValue = 1;
118121
InputImagePixelType m_OutsideValue = 0;
122+
bool m_EnforceManifold = true;
119123

120124
}; // end of FixTopologyCarveOutside class
121125

Core/itkFixTopologyCarveOutside.hxx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,13 @@ void FixTopologyCarveOutside<TInputImage, TOutputImage>::ComputeThinImage()
154154
bool can_carve = false;
155155
if (EulerInvariant(ot.GetNeighborhood(), m_InsideValue))
156156
{
157-
// Check if point is simple (deletion does not change connectivity in the 3x3x3 neighborhood)
158-
if (CCInvariant(ot.GetNeighborhood(), m_InsideValue))
157+
if (!m_EnforceManifold || !NonmanifoldRemove(ot.GetNeighborhood(), m_InsideValue))
159158
{
160-
can_carve = true;
159+
// Check if point is simple (deletion does not change connectivity in the 3x3x3 neighborhood)
160+
if (CCInvariant(ot.GetNeighborhood(), m_InsideValue))
161+
{
162+
can_carve = true;
163+
}
161164
}
162165
}
163166

iSeg/SurfaceViewerWidget.cpp

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,18 @@
2828
#include <vtkPointData.h>
2929
#include <vtkUnsignedShortArray.h>
3030

31+
#include <vtkDecimatePro.h>
3132
#include <vtkEventQtSlotConnect.h>
3233
#include <vtkInteractorStyleTrackballCamera.h>
3334
#include <vtkLookupTable.h>
35+
#include <vtkPolyDataConnectivityFilter.h>
3436
#include <vtkPolyDataMapper.h>
3537
#include <vtkPropPicker.h>
3638
#include <vtkProperty.h>
3739
#include <vtkQuadricLODActor.h>
3840
#include <vtkRenderWindow.h>
3941
#include <vtkRenderer.h>
4042

41-
#include <vtkPolyDataConnectivityFilter.h>
42-
4343
#include <vtkAutoInit.h>
4444
#ifdef ISEG_VTK_OPENGL2
4545
VTK_MODULE_INIT(vtkRenderingOpenGL2);
@@ -77,8 +77,7 @@ void transform_slices_vtk(const std::vector<TIn*>& slices, size_t slice_size, TA
7777
}
7878
}
7979

80-
enum eActions
81-
{
80+
enum eActions {
8281
kSelectTissue,
8382
kGotoSlice,
8483
kMarkPoint
@@ -111,6 +110,21 @@ SurfaceViewerWidget::SurfaceViewerWidget(SlicesHandler* hand3D1, eInputType inpu
111110
lb_connectivity_count = new QLabel("");
112111
lb_connectivity_count->setToolTip("Number of connected regions");
113112

113+
reduction = new QLineEdit(QString::number(0));
114+
reduction->setValidator(new QIntValidator(0, 100));
115+
reduction->setToolTip("Reduction of number triangles in percent.");
116+
117+
// set default reduction depending number of voxels.
118+
const auto N = static_cast<float>(hand3D->return_area()) * hand3D->num_slices();
119+
if (N > 1e8)
120+
{
121+
reduction->setText(QString::number(80));
122+
}
123+
else if (N > 1e6)
124+
{
125+
reduction->setText(QString::number(50));
126+
}
127+
114128
// layout
115129
auto vbox = new QVBoxLayout;
116130
vbox->addWidget(vtkWidget);
@@ -120,6 +134,10 @@ SurfaceViewerWidget::SurfaceViewerWidget(SlicesHandler* hand3D1, eInputType inpu
120134
transparency_hbox->addWidget(sl_trans);
121135
vbox->addLayout(transparency_hbox);
122136

137+
auto reduction_hbox = new QHBoxLayout;
138+
reduction_hbox->addWidget(reduction);
139+
vbox->addLayout(reduction_hbox);
140+
123141
if (input_type == kSource)
124142
{
125143
auto lb_thresh = new QLabel("Contour iso-value");
@@ -145,9 +163,10 @@ SurfaceViewerWidget::SurfaceViewerWidget(SlicesHandler* hand3D1, eInputType inpu
145163
setLayout(vbox);
146164

147165
// connections
148-
QObject::connect(sl_trans, SIGNAL(sliderReleased()), this, SLOT(transp_changed()));
149-
QObject::connect(bt_update, SIGNAL(clicked()), this, SLOT(reload()));
150-
QObject::connect(bt_connectivity, SIGNAL(clicked()), this, SLOT(split_surface()));
166+
connect(sl_trans, SIGNAL(sliderReleased()), this, SLOT(transp_changed()));
167+
connect(bt_update, SIGNAL(clicked()), this, SLOT(reload()));
168+
connect(bt_connectivity, SIGNAL(clicked()), this, SLOT(split_surface()));
169+
connect(reduction, SIGNAL(editingFinished()), this, SLOT(reduction_changed));
151170

152171
// setup vtk scene
153172
ren3D = vtkSmartPointer<vtkRenderer>::New();
@@ -177,20 +196,24 @@ SurfaceViewerWidget::SurfaceViewerWidget(SlicesHandler* hand3D1, eInputType inpu
177196
input = vtkSmartPointer<vtkImageData>::New();
178197
discreteCubes = vtkSmartPointer<vtkDiscreteFlyingEdges3D>::New();
179198
cubes = vtkSmartPointer<vtkFlyingEdges3D>::New();
199+
decimate = vtkSmartPointer<vtkDecimatePro>::New();
200+
decimate->PreserveTopologyOn();
201+
decimate->BoundaryVertexDeletionOff();
202+
decimate->SplittingOff();
203+
decimate->SetTargetReduction(reduction->text().toDouble() / 100.0);
180204

181205
load();
182206

183207
vtkWidget->GetRenderWindow()->Render();
184208
}
185209

186-
SurfaceViewerWidget::~SurfaceViewerWidget()
210+
SurfaceViewerWidget::~SurfaceViewerWidget()
187211
{
188-
189212
}
190213

191214
bool SurfaceViewerWidget::isOpenGLSupported()
192215
{
193-
// todo: check e.g. via some helper process
216+
// todo: check e.g. via some helper process
194217
return true;
195218
}
196219

@@ -278,7 +301,9 @@ void SurfaceViewerWidget::load()
278301
cubes->SetInputData(input);
279302
cubes->SetValue(0, range[0] + 0.01 * (range[1] - range[0]) * sl_thresh->value());
280303

281-
mapper->SetInputConnection(cubes->GetOutputPort());
304+
decimate->SetInputConnection(cubes->GetOutputPort());
305+
306+
mapper->SetInputConnection(decimate->GetOutputPort());
282307
mapper->ScalarVisibilityOff();
283308
}
284309
else
@@ -290,8 +315,9 @@ void SurfaceViewerWidget::load()
290315
// merge duplicate points (check if necessary)
291316
// connectivity filter & set random colors
292317
// mapper set input to connectivity output
318+
decimate->SetInputConnection(discreteCubes->GetOutputPort());
293319

294-
mapper->SetInputConnection(discreteCubes->GetOutputPort());
320+
mapper->SetInputConnection(decimate->GetOutputPort());
295321
if (input_type == kTarget)
296322
{
297323
mapper->ScalarVisibilityOff();
@@ -322,7 +348,7 @@ void SurfaceViewerWidget::split_surface()
322348

323349
auto num_regions = connectivity->GetNumberOfExtractedRegions();
324350
ISEG_INFO("Number of disconnected regions: " << num_regions);
325-
351+
326352
// attach lookuptable
327353
auto lut = vtkSmartPointer<vtkLookupTable>::New();
328354
lut->SetNumberOfTableValues(num_regions);
@@ -336,7 +362,7 @@ void SurfaceViewerWidget::split_surface()
336362

337363
auto output = vtkSmartPointer<vtkPolyData>::New();
338364
output->ShallowCopy(connectivity->GetOutput());
339-
365+
340366
mapper->SetInputData(output);
341367
mapper->ScalarVisibilityOn();
342368
mapper->SetColorModeToMapScalars();
@@ -555,6 +581,13 @@ void SurfaceViewerWidget::thresh_changed()
555581
}
556582
}
557583

584+
void SurfaceViewerWidget::reduction_changed()
585+
{
586+
decimate->SetTargetReduction(reduction->text().toDouble() / 100.0);
587+
588+
vtkWidget->GetRenderWindow()->Render();
589+
}
590+
558591
int SurfaceViewerWidget::get_picked_tissue() const
559592
{
560593
double* worldPosition = picker->GetPickPosition();
@@ -580,4 +613,4 @@ int SurfaceViewerWidget::get_picked_tissue() const
580613
return -1;
581614
}
582615

583-
}// namespace iseg
616+
} // namespace iseg

iSeg/SurfaceViewerWidget.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class QVTKWidget;
2424
class QVTKInteractor;
2525
class QSlider;
2626
class QLabel;
27+
class QLineEdit;
2728
class QPushButton;
2829
class QCheckBox;
2930

@@ -32,6 +33,7 @@ class vtkInteractorStyleTrackballCamera;
3233
class vtkImageData;
3334
class vtkFlyingEdges3D;
3435
class vtkDiscreteFlyingEdges3D;
36+
class vtkDecimatePro;
3537
class vtkPolyDataMapper;
3638
class vtkRenderer;
3739
class vtkEventQtSlotConnect;
@@ -75,6 +77,7 @@ public slots:
7577
protected slots:
7678
void transp_changed();
7779
void thresh_changed();
80+
void reduction_changed();
7881
void popup(vtkObject* obj, unsigned long, void* client_data, void*, vtkCommand* command);
7982
void select_action(QAction*);
8083

@@ -90,6 +93,7 @@ protected slots:
9093
QVTKWidget* vtkWidget;
9194
QSlider* sl_trans;
9295
QSlider* sl_thresh;
96+
QLineEdit* reduction;
9397
QPushButton* bt_update;
9498
QPushButton* bt_connectivity;
9599
QLabel* lb_connectivity_count;
@@ -102,6 +106,7 @@ protected slots:
102106
vtkSmartPointer<vtkInteractorStyleTrackballCamera> style;
103107
vtkSmartPointer<vtkDiscreteFlyingEdges3D> discreteCubes;
104108
vtkSmartPointer<vtkFlyingEdges3D> cubes;
109+
vtkSmartPointer<vtkDecimatePro> decimate;
105110
vtkSmartPointer<vtkPolyDataMapper> mapper;
106111
vtkSmartPointer<vtkActor> actor;
107112
vtkSmartPointer<vtkLookupTable> lut;

0 commit comments

Comments
 (0)