File: ContourWidget.cpp

package info (click to toggle)
camitk 5.2.0-5
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 358,388 kB
  • sloc: cpp: 86,984; xml: 1,295; sh: 1,280; ansic: 142; makefile: 112; perl: 84; sed: 20
file content (332 lines) | stat: -rw-r--r-- 12,716 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2024 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/

#include "ContourWidget.h"
#include "ContourWidgetVtkCommand.h"

// -- vtk stuff --
// disable warning generated by clang about the surrounded headers
#include <CamiTKDisableWarnings>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderWindow.h>
#include <vtkOrientedGlyphContourRepresentation.h>
#include <vtkWidgetEventTranslator.h>
#include <vtkEvent.h>
#include <vtkBoundedPlanePointPlacer.h>
#include <vtkProperty.h>
#include <CamiTKReEnableWarnings>

#include <vtkContourWidget.h>
#include <vtkPolyData.h>

//-- Qt
#include <QBoxLayout>
#include <QFrame>

// -- Application --
#include <Application.h>
#include <InteractiveSliceViewer.h>
#include <Component.h>
#include <MeshComponent.h>
#include <ImageComponent.h>
#include <SingleImageComponent.h>

#include <Log.h>
#include <Property.h>
#include <RendererWidget.h>
#include <ActionWidget.h>

using namespace camitk;

// --------------- constructor -------------------
ContourWidget::ContourWidget(ActionExtension* extension) : Action(extension) {
    // Setting name, description and input component
    setName("Contour Widget");
    setDescription("Add a VTK Contour widget to a 2D viewer.<ul><li>Left Click: Add/select a point</li><li>Right Click: Add final point</li><li>Middle Click: Translate</li><li>Reset Contour: Start again</li><li>Close Contour: Either join start/end points or click the push button</li></ul>");
    setComponentClassName("ImageComponent");

    // Setting classification family and tags
    setFamily("Tutorial");
    addTag("Demo");
    addTag("Segmentation");
    addTag("2D Interaction");
    addTag("VTK Widget");

    setProperty("Contour Color", QVariant(QColor(250.0, 150.0, 50.0))); // color of the contour (default is orange)
    setProperty("Line Width", QVariant(2.0));

    Property* nbOfPointsProperty = new Property("Number Of Points", 0, "Number of points in the current contour", "");
    nbOfPointsProperty->setReadOnly(true);
    addParameter(nbOfPointsProperty);

    viewer = Axial;
    contourWidget = nullptr;
    contourWidgetCommand = nullptr;
    transformFromAxialToWorld = nullptr;
    recording = false;

    //-- widget lazy instantiation
    informationFrame = nullptr;

    //-- currently selected image
    currentImage = nullptr;
    currentMesh = nullptr;
}

// --------------- getViewer -------------------
ContourWidget::Viewer ContourWidget::getViewer() const {
    return viewer;
}

// --------------- setWiewer -------------------
void ContourWidget::setWiewer(const ContourWidget::Viewer viewer) {
    bool viewerChanged;
    viewerChanged = (this->viewer != viewer);

    this->viewer = viewer;

    if (viewerChanged) {
        initContour();
    }
}

// --------------- getWidget -------------------
QWidget* ContourWidget::getWidget() {
    // update image
    ImageComponent* selectedImage = dynamic_cast<ImageComponent*>(getTargets().last());

    // check if the current image is still the same
    if (selectedImage != currentImage) {
        // update image
        currentImage = selectedImage;
        // initialize the contour
        initContour();
    }

    // create widget
    if (!informationFrame) {
        //-- the frame
        informationFrame = new QFrame();
        informationFrame->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
        informationFrame->setLineWidth(3);

        //-- the vertical layout, put every GUI elements in it
        auto* informationFrameLayout = new QVBoxLayout();

        // add the default action widget
        informationFrameLayout->addWidget(Action::getWidget());

        // add a reset button
        QPushButton* reset = new QPushButton("Reset Contour");
        informationFrameLayout->addWidget(reset);
        QObject::connect(reset, SIGNAL(released()), this, SLOT(initContour()));

        QPushButton* closeContour = new QPushButton("Close Contour");
        informationFrameLayout->addWidget(closeContour);
        QObject::connect(closeContour, SIGNAL(released()), this, SLOT(closeContour()));

        //-- set the layout for the action widget
        informationFrame->setLayout(informationFrameLayout);
    }

    return informationFrame;
}

// --------------- apply -------------------
Action::ApplyStatus ContourWidget::apply() {
    //-- update the contour
    if (contourWidget != nullptr) {
        updateContour();
    }

    //-- create/update the transformed mesh
    if (currentMesh == nullptr) {
        // create mesh for the first time
        QString meshName = "";
        if (currentImage != nullptr) {
            meshName = currentImage->getName();
        }
        if (transformFromAxialToWorld != nullptr) {
            currentMesh = new MeshComponent(transformFromAxialToWorld->GetOutput(), meshName + " Contour");
        }
        else {
            currentMesh = new MeshComponent(nullptr, meshName + " Contour");
        }
    }
    else {
        // update the current mesh (here you can also concatenate contours instead of replacing them)
        currentMesh->setPointSet(transformFromAxialToWorld->GetOutput());
    }

    //-- update color
    QColor contourColor = property("Contour Color").value<QColor>();
    currentMesh->setColor(contourColor.redF(), contourColor.greenF(), contourColor.blueF());

    //-- update 3D line width
    if (currentMesh->getActor(InterfaceGeometry::Surface) != nullptr) {
        currentMesh->getActor(InterfaceGeometry::Surface)->GetProperty()->SetLineWidth(property("Line Width").toFloat());
    }

    Application::refresh();

    return SUCCESS;
}

// --------------- initContour -------------------
void ContourWidget::initContour() {

    //-- if already present reset observers
    if (contourWidget) {
        contourWidget->RemoveObserver(contourWidgetCommand);
        contourWidget->Off();
        contourWidget = nullptr;
        contourWidgetCommand = nullptr;
        transformFromAxialToWorld = nullptr;
    }

    //-- create contour
    contourWidget = vtkSmartPointer<vtkContourWidget>::New();

    //-- set the interactor depending on the viewer chosen by the user
    // Also computes the z center = the correct position in the viewer so that the contour widget
    // can be seen on top of the other widgets (hence use the pixel actor position)
    vtkRenderWindowInteractor* interactor = nullptr;
    SingleImageComponent* viewedSIC = nullptr;
    double zCenter; // center of the image in the Z direction

    if (currentImage != nullptr && Application::getViewer("Axial Viewer") != nullptr) {
        if (viewer == Coronal) {
#if VTK_MAJOR_VERSION < 9
            interactor = dynamic_cast<InteractiveSliceViewer*>(Application::getViewer("Coronal Viewer"))->getRendererWidget()->GetRenderWindow()->GetInteractor();
#else
            interactor = dynamic_cast<InteractiveSliceViewer*>(Application::getViewer("Coronal Viewer"))->getRendererWidget()->renderWindow()->GetInteractor();
#endif
            viewedSIC = currentImage->getCoronalSlices();
        }
        else {
            if (viewer == Sagittal) {
#if VTK_MAJOR_VERSION < 9
                interactor = dynamic_cast<InteractiveSliceViewer*>(Application::getViewer("Sagittal Viewer"))->getRendererWidget()->GetRenderWindow()->GetInteractor();
#else
                interactor = dynamic_cast<InteractiveSliceViewer*>(Application::getViewer("Sagittal Viewer"))->getRendererWidget()->renderWindow()->GetInteractor();
#endif
                viewedSIC = currentImage->getSagittalSlices();
            }
            else {
#if VTK_MAJOR_VERSION < 9
                interactor = dynamic_cast<InteractiveSliceViewer*>(Application::getViewer("Axial Viewer"))->getRendererWidget()->GetRenderWindow()->GetInteractor();
#else
                interactor = dynamic_cast<InteractiveSliceViewer*>(Application::getViewer("Axial Viewer"))->getRendererWidget()->renderWindow()->GetInteractor();
#endif
                viewedSIC = currentImage->getAxialSlices();
            }
        }
    }
    contourWidget->SetInteractor(interactor);

    //-- create 3D representation
    contourWidget->CreateDefaultRepresentation();
    contourWidget->ContinuousDrawOn();
    contourWidget->On();

    //-- add the callback
    contourWidgetCommand = new ContourWidgetVtkCommand(this);
    contourWidget->AddObserver(vtkCommand::EndInteractionEvent, contourWidgetCommand);

    //-- move the representation forward in the z direction in order to see it in the interactive viewers
    if (viewedSIC != nullptr) {
        zCenter = viewedSIC->getPixelActor()->GetCenter()[2];
    }
    else {
        CAMITK_WARNING(tr("No current image or current image has no slice in the current plane (%1)").arg(QString::number(viewer)))
        zCenter = 0.0;
    }

    vtkSmartPointer<vtkOrientedGlyphContourRepresentation> contourRep = vtkOrientedGlyphContourRepresentation::SafeDownCast(contourWidget->GetRepresentation());
    vtkSmartPointer<vtkBoundedPlanePointPlacer> frontPlanePlacer = vtkSmartPointer<vtkBoundedPlanePointPlacer>::New();
    contourRep->SetPointPlacer(frontPlanePlacer);
    frontPlanePlacer->SetProjectionNormalToZAxis();
    frontPlanePlacer->SetProjectionPosition(zCenter);

    //-- init interactor
    if (interactor != nullptr) {
        interactor->ReInitialize();
    }

    //-- transform it relatively to the slice type
    // contourWidget -> GetRepresentation (== contourRep) => PolyData
    // + Transform from image frame to world
    // = 3D representation of the contour at the right place
    //
    // The transformation is required as the contour is a widget drawn in the z=0 plane
    // of the axial (or coronal or Sagittal) plane.
    //
    // It has to be transformed back to 3D using the current image frame
    transformFromAxialToWorld = vtkSmartPointer<vtkTransformPolyDataFilter>::New();
    if (currentImage != nullptr) {
        transformFromAxialToWorld->SetTransform(currentImage->getTransformFromWorld());
    }
    else {
        vtkSmartPointer<vtkTransform> idTransform = vtkSmartPointer<vtkTransform>::New();
        transformFromAxialToWorld->SetTransform(idTransform);
    }
    transformFromAxialToWorld->SetInputData(contourRep->GetContourRepresentationAsPolyData());

    //-- update color and line width
    updateContour();

    Application::refresh();
}

// --------------- updateContour -------------------
void ContourWidget::updateContour() {
    // change color of the VTK widget representation and current mesh using the current user choice
    QColor contourColor = property("Contour Color").value<QColor>();

    vtkSmartPointer<vtkOrientedGlyphContourRepresentation> contourRep = vtkOrientedGlyphContourRepresentation::SafeDownCast(contourWidget->GetRepresentation());
    contourRep->GetLinesProperty()->SetColor(contourColor.redF(), contourColor.greenF(), contourColor.blueF());

    // change line width
    contourRep->GetLinesProperty()->SetLineWidth(property("Line Width").toFloat());

    // update the fransformation filter
    transformFromAxialToWorld->Update();
}

// --------------- closeContour -------------------
void ContourWidget::closeContour() {
    contourWidget->CloseLoop();
}

// --------------- updateWidget -------------------
void ContourWidget::updateWidget() {
    // update action's widget from modified property (this is not the "usual" way, normally
    // the user modify some property in the widget and the action gets the value to do
    // something. In this case, the action's property is modified by some external
    // mechanism (not the user), e.g. here the vtContourWidget, therefore the action's widget has
    // to be updated
    dynamic_cast<camitk::ActionWidget*>(Action::getWidget())->update();
}