2.12.2021, 9:00 - 11:00: Due to updates GitLab may be unavailable for some minutes between 09:00 and 11:00.

geometry1dtransferfunctioneditor.cpp 16.1 KB
Newer Older
1
2
// ================================================================================================
// 
schultezub's avatar
schultezub committed
3
// This file is part of the CAMPVis Software Framework.
4
5
// 
// If not explicitly stated otherwise: Copyright (C) 2012, all rights reserved,
schultezub's avatar
schultezub committed
6
//      Christian Schulte zu Berge <christian.szb@in.tum.de>
7
//      Chair for Computer Aided Medical Procedures
8
9
//      Technische Universität München
//      Boltzmannstr. 3, 85748 Garching b. München, Germany
schultezub's avatar
schultezub committed
10
// For a full list of authors and contributors, please refer to the file "AUTHORS.txt".
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 
// The licensing of this softare is not yet resolved. Until then, redistribution in source or
// binary forms outside the CAMP chair is not permitted, unless explicitly stated in legal form.
// However, the names of the original authors and the above copyright notice must retain in its
// original state in any case.
// 
// Legal disclaimer provided by the BSD license:
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// 
// ================================================================================================

schultezub's avatar
schultezub committed
30
#include "geometry1dtransferfunctioneditor.h"
31
32
33

#include "tgt/assert.h"
#include "tgt/shadermanager.h"
34
#include "tgt/glcontextmanager.h"
35
36
37
#include "tgt/qt/qtthreadedcanvas.h"

#include "application/gui/qtcolortools.h"
38
39
#include "application/gui/properties/tfgeometrymanipulator.h"

schultezub's avatar
schultezub committed
40
41
#include "core/classification/geometry1dtransferfunction.h"
#include "core/classification/tfgeometry1d.h"
42
#include "core/datastructures/imagerepresentationlocal.h"
43
44
45
#include "core/properties/transferfunctionproperty.h"
#include "core/tools/opengljobprocessor.h"

46
#include <QCheckBox>
47
48
#include <QGridLayout>
#include <QLabel>
49
50
#include <QPushButton>
#include <QVBoxLayout>
51

schultezub's avatar
schultezub committed
52
namespace campvis {
53

schultezub's avatar
schultezub committed
54
    Geometry1DTransferFunctionEditor::Geometry1DTransferFunctionEditor(Geometry1DTransferFunction* tf, QWidget* parent /*= 0*/)
55
        : AbstractTransferFunctionEditor(tf, parent)
56
        , _logScale(true)
57
58
        , _layout(0)
        , _canvas(0)
59
60
        , _lblIntensityLeft(0)
        , _lblIntensityRight(0)
61
        , _btnAddGeometry(0)
62
63
        , _btnRemoveGeometry(0)
        , _cbLogScale(0)
64
    {
65
        _selectedGeometry = 0;
66
        setupGUI();
schultezub's avatar
schultezub committed
67
        tf->s_geometryCollectionChanged.connect(this, &Geometry1DTransferFunctionEditor::onGeometryCollectionChanged);
68
        updateManipulators();
69
        setEventTypes(tgt::Event::MOUSEPRESSEVENT);
70
71
    }

schultezub's avatar
schultezub committed
72
    Geometry1DTransferFunctionEditor::~Geometry1DTransferFunctionEditor() {
73
74
        tbb::mutex::scoped_lock lock(_localMutex);

75
        // clearEventListeners and delete former stuff
76
77
78
79
80
81
82
83
        _selectedGeometry = 0;
        for (std::vector<AbstractTFGeometryManipulator*>::iterator it = _manipulators.begin(); it != _manipulators.end(); ++it) {
            if (WholeTFGeometryManipulator* tester = dynamic_cast<WholeTFGeometryManipulator*>(*it)) {
                tester->s_selected.disconnect(this);
            }
            delete *it;
        }

schultezub's avatar
schultezub committed
84
        Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
85
        gtf->s_geometryCollectionChanged.disconnect(this);
86
87
88
89
90

        if (OpenGLJobProcessor::isInited())
            GLJobProc.deregisterContext(_canvas);
        if (tgt::GlContextManager::isInited())
            tgt::GlContextManager::getRef().removeContext(_canvas);
91
92
    }

schultezub's avatar
schultezub committed
93
94
    void Geometry1DTransferFunctionEditor::updateWidgetFromProperty() {
        Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
95
96
        _lblIntensityLeft->setText(QString::number(gtf->getIntensityDomain().x));
        _lblIntensityRight->setText(QString::number(gtf->getIntensityDomain().y));
97
        invalidate();
98
99
    }

schultezub's avatar
schultezub committed
100
101
102
    void Geometry1DTransferFunctionEditor::paint() {
        Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
        const std::vector<TFGeometry1D*>& geometries = gtf->getGeometries();
103
104
        const tgt::vec2& intensityDomain = gtf->getIntensityDomain();

105
        // TODO: get rid of intermediate mode?
106
        glPushAttrib(GL_ALL_ATTRIB_BITS);
107
108
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
109
110
        glViewport(0, 0, _canvas->width(), _canvas->height());

111
112
        glMatrixMode(GL_PROJECTION);
        glPushMatrix();
113
        glOrtho(0, 1 , 0, 1, -1, 1);
114
115
116
117
        glClearColor(1.f, 1.f, 1.f, 1.f);
        glClear(GL_COLOR_BUFFER_BIT);
        LGL_ERROR;

118
        // renderIntoEditor TF geometries
119
120
121
122
123
        {
            tbb::mutex::scoped_lock lock(_localMutex);
            for (std::vector<TFGeometry1D*>::const_iterator it = geometries.begin(); it != geometries.end(); ++it) {
                (*it)->renderIntoEditor();
            }
124
125
126
127
128
129
130
131
        }

        // render histogram if existent
        const AbstractTransferFunction::IntensityHistogramType* ih = gtf->getIntensityHistogram();
        if (ih != 0) {
            size_t numBuckets = ih->getNumBuckets(0);
            if (numBuckets > 0) {
                float maxFilling = static_cast<float>(ih->getMaxFilling());
132
133
                if (_logScale)
                    maxFilling = log(maxFilling);
134
135
136

                float xl = static_cast<float>(0.f) / static_cast<float>(numBuckets);
                float xr = 0.f;
137
138
139
                float yl = (_logScale 
                    ? log(static_cast<float>(ih->getNumElements(0))) / maxFilling
                    : static_cast<float>(ih->getNumElements(0)) / maxFilling);
140
141
                float yr = 0.f;

142

143
144
145
146
                glBegin(GL_QUADS);
                glColor4f(1.f, .75f, 0.f, .5f);
                for (size_t i = 1; i < numBuckets; ++i) {
                    xr = static_cast<float>(i) / static_cast<float>(numBuckets);
147
                    yr = (_logScale 
schultezub's avatar
schultezub committed
148
                        ? std::max(0.f, static_cast<float>(log(static_cast<float>(ih->getNumElements(i)))) / maxFilling)
149
150
                        : static_cast<float>(ih->getNumElements(i)) / maxFilling);
                    
151
152
153
154
155
156
157
                    glVertex2f(xl, 0.f);
                    glVertex2f(xl, yl);
                    glVertex2f(xr, yr);
                    glVertex2f(xr, 0.f);

                    xl = xr;
                    yl = yr;
158
                }
159
                glEnd();
160
            }
161
162
        }

163
164
165
166
        {
            tbb::mutex::scoped_lock lock(_localMutex);

            // render selected geometry
schultezub's avatar
schultezub committed
167
168
169
170
            WholeTFGeometryManipulator* selectedGeometry = _selectedGeometry;
            if (selectedGeometry != 0) {
                // the copy is deliberate for improved thread safety (the whole design is a little messy here...)
                std::vector<tgt::vec2> helperPoints = selectedGeometry->getHelperPoints();
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
                glColor4ub(0, 0, 0, 196);
                glEnable(GL_LINE_STIPPLE);
                glLineStipple(1, 0xFAFA);
                glBegin(GL_LINE_LOOP);
                for (std::vector<tgt::vec2>::const_iterator it = helperPoints.begin(); it != helperPoints.end(); ++it)
                    glVertex2fv(it->elem);
                glEnd();
                glDisable(GL_LINE_STIPPLE);
            }

            glPopMatrix();

            glPushMatrix();
            glOrtho(0, _canvas->width(), 0, _canvas->height(), -1, 1);
            // render manipulators
            for (std::vector<AbstractTFGeometryManipulator*>::iterator it = _manipulators.begin(); it != _manipulators.end(); ++it) {
                (*it)->render();
            }
            glPopMatrix();
190
191
192
193
194
195
        }

        LGL_ERROR;
        glPopAttrib();
    }

schultezub's avatar
schultezub committed
196
    void Geometry1DTransferFunctionEditor::sizeChanged(const tgt::ivec2& size) {
197
        tbb::mutex::scoped_lock lock(_localMutex);
198
199
200
        for (std::vector<AbstractTFGeometryManipulator*>::iterator it = _manipulators.begin(); it != _manipulators.end(); ++it) {
            (*it)->setViewportSize(size);
        }
201
202
203
        invalidate();
    }

schultezub's avatar
schultezub committed
204
    void Geometry1DTransferFunctionEditor::mousePressEvent(tgt::MouseEvent* e) {
205
        if (_selectedGeometry != 0 && e->modifiers() & tgt::Event::CTRL) {
schultezub's avatar
schultezub committed
206
            // add a control point on CTRL+Click
schultezub's avatar
schultezub committed
207
208
209
            TFGeometry1D* g = _selectedGeometry->getGeometry();
            std::vector<TFGeometry1D::KeyPoint>& kpts = g->getKeyPoints();
            TFGeometry1D::KeyPoint kp(static_cast<float>(e->x()) / static_cast<float>(_canvas->width()), tgt::col4(255));
schultezub's avatar
schultezub committed
210
            std::vector<TFGeometry1D::KeyPoint>::iterator lb = std::upper_bound(kpts.begin(), kpts.end(), kp);
211
212
213
214
215
216
217
218
219
220
221
222
223
224
            if (lb != kpts.end()) {
                kp._color = lb->_color;
            }
            else {
                kp._color = kpts.back()._color;
            }
            float alpha = tgt::clamp(static_cast<float>(_canvas->height() - e->y()) / static_cast<float>(_canvas->height()), 0.f, 1.f);
            kp._color.a = static_cast<uint8_t>(alpha * 255.f);
            kpts.insert(lb, kp);
            updateManipulators();
            g->s_changed();
        }
        else {
            _selectedGeometry = 0;
225
            invalidate();
226
227
228
229
            e->ignore();
        }
    }

230
231
232
233
    void Geometry1DTransferFunctionEditor::repaint() {
        invalidate();
    }

schultezub's avatar
schultezub committed
234
    void Geometry1DTransferFunctionEditor::invalidate() {
235
        GLJobProc.enqueueJob(_canvas, makeJobOnHeap(this, &Geometry1DTransferFunctionEditor::paint), OpenGLJobProcessor::PaintJob);
236
237
    }

schultezub's avatar
schultezub committed
238
239
    void Geometry1DTransferFunctionEditor::setupGUI() {
        Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
240
241
242
243
244
245
246
247
248
249
250

        _layout = new QGridLayout(this);
        setLayout(_layout);

        QLabel* lblOpacityTop = new QLabel(tr("100%"), this);
        _layout->addWidget(lblOpacityTop, 1, 0, 1, 1, Qt::AlignRight);
        QLabel* lblOpacity = new QLabel(tr("Opacity"), this);
        _layout->addWidget(lblOpacity, 2, 0, 1, 1, Qt::AlignRight);
        QLabel* lblOpacityBottom = new QLabel(tr("0%"), this);
        _layout->addWidget(lblOpacityBottom, 3, 0, 1, 1, Qt::AlignRight);

251
252
253
        _canvas = dynamic_cast<tgt::QtThreadedCanvas*>(tgt::GlContextManager::getRef().createContext("tfcanvas", "", tgt::ivec2(256, 128), tgt::GLCanvas::RGBA_BUFFER, false));
        tgtAssert(_canvas != 0, "Could not cast to QtThreadedCanvas*, something is wrong here!");

254
255
256
257
258
259
260
261
262
263
264
        GLJobProc.registerContext(_canvas);
        _canvas->setPainter(this, false);
        _layout->addWidget(_canvas, 1, 1, 3, 3);

        _lblIntensityLeft = new QLabel(QString::number(gtf->getIntensityDomain().x), this);
        _layout->addWidget(_lblIntensityLeft, 4, 1, 1, 1, Qt::AlignLeft);
        QLabel* lblIntensity = new QLabel(tr("Intensity"), this);
        _layout->addWidget(lblIntensity, 4, 2, 1, 1, Qt::AlignHCenter);
        _lblIntensityRight = new QLabel(QString::number(gtf->getIntensityDomain().y), this);
        _layout->addWidget(_lblIntensityRight, 4, 3, 1, 1, Qt::AlignRight);

265
266
267
268
269
270
271
272
273
        QVBoxLayout* buttonLayout = new QVBoxLayout(); // TODO: check whether buttonLayout will be deleted by Qt's GC!
        _layout->addLayout(buttonLayout, 1, 4, 1, 3, Qt::AlignTop);

        _btnAddGeometry = new QPushButton(tr("Add Geometry"), this);
        buttonLayout->addWidget(_btnAddGeometry);
        connect(_btnAddGeometry, SIGNAL(clicked()), this, SLOT(onBtnAddGeometryClicked()));
        _btnRemoveGeometry = new QPushButton(tr("Remove Geometry"), this);
        buttonLayout->addWidget(_btnRemoveGeometry);
        connect(_btnRemoveGeometry, SIGNAL(clicked()), this, SLOT(onBtnRemoveGeometryClicked()));
274
275
276
277
        _cbLogScale = new QCheckBox(tr("Logarithmic Scale"), this);
        _cbLogScale->setChecked(true);
        buttonLayout->addWidget(_cbLogScale);
        connect(_cbLogScale, SIGNAL(stateChanged(int)), this, SLOT(onCbLogScaleStateChanged(int)));
278
279
280
281
        _layout->setColumnStretch(2, 1);
        _layout->setRowStretch(2, 1);
    }

schultezub's avatar
schultezub committed
282
    void Geometry1DTransferFunctionEditor::updateManipulators() {
283
284
        tbb::mutex::scoped_lock lock(_localMutex);

285
        // clearEventListeners and delete former stuff
286
        _selectedGeometry = 0;
287
        _canvas->getEventHandler()->clearEventListeners();
288
        for (std::vector<AbstractTFGeometryManipulator*>::iterator it = _manipulators.begin(); it != _manipulators.end(); ++it) {
289
290
291
            if (WholeTFGeometryManipulator* tester = dynamic_cast<WholeTFGeometryManipulator*>(*it)) {
            	tester->s_selected.disconnect(this);
            }
292
293
294
295
            delete *it;
        }
        _manipulators.clear();

schultezub's avatar
schultezub committed
296
297
298
        Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
        const std::vector<TFGeometry1D*>& geometries = gtf->getGeometries();
        for (std::vector<TFGeometry1D*>::const_iterator git = geometries.begin(); git != geometries.end(); ++git) {
299
            // Add manipulator for the whole geometry and register it as event handler:
300
            WholeTFGeometryManipulator* wtf = new WholeTFGeometryManipulator(_canvas->getSize(), *git);
301
            _manipulators.push_back(wtf);
302
            _canvas->getEventHandler()->addEventListenerToFront(wtf);
schultezub's avatar
schultezub committed
303
            wtf->s_selected.connect(this, &Geometry1DTransferFunctionEditor::onWholeTFGeometryManipulatorSelected);
304
305

            // Add a manipulator for each KeyPoint and register it as event handler:
schultezub's avatar
schultezub committed
306
            for (std::vector<TFGeometry1D::KeyPoint>::iterator kpit = (*git)->getKeyPoints().begin(); kpit != (*git)->getKeyPoints().end(); ++kpit) {
307
                _manipulators.push_back(new KeyPointManipulator(_canvas->getSize(), *git, kpit));
308
                _canvas->getEventHandler()->addEventListenerToFront(_manipulators.back());
309
310
311
            }
        }

312
        _canvas->getEventHandler()->addEventListenerToFront(this);
313
314
    }

schultezub's avatar
schultezub committed
315
    void Geometry1DTransferFunctionEditor::onGeometryCollectionChanged() {
316
317
318
        updateManipulators();
    }

schultezub's avatar
schultezub committed
319
    void Geometry1DTransferFunctionEditor::onWholeTFGeometryManipulatorSelected(WholeTFGeometryManipulator* wtf /* :) */) {
320
        _selectedGeometry = wtf;
321
        invalidate();
322
323
    }

schultezub's avatar
schultezub committed
324
325
326
    void Geometry1DTransferFunctionEditor::onBtnAddGeometryClicked() {
        Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
        gtf->addGeometry(TFGeometry1D::createQuad(tgt::vec2(.4f, .6f), tgt::col4(196), tgt::col4(196)));
327
328
    }

schultezub's avatar
schultezub committed
329
    void Geometry1DTransferFunctionEditor::onBtnRemoveGeometryClicked() {
330
331
332
        if (_selectedGeometry != 0) {
            // to get the signal-slots disconnected in the correct order and avoid double deletion,
            // this is getting a little messy and cumbersome:
schultezub's avatar
schultezub committed
333
334
            Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
            TFGeometry1D* geometryToRemove = _selectedGeometry->getGeometry();
335
336
337
338
339
340
341
342
343

            {
                tbb::mutex::scoped_lock lock(_localMutex);

                for (std::vector<AbstractTFGeometryManipulator*>::iterator it = _manipulators.begin(); it != _manipulators.end(); ++it) {
                    if (*it == _selectedGeometry) {
                        _manipulators.erase(it);
                        break;
                    }
344
                }
345
346
                delete _selectedGeometry;
                _selectedGeometry = 0;
347
            }
348

349
350
351
352
            gtf->removeGeometry(geometryToRemove);
        }
    }

353
354
355
356
357
    void Geometry1DTransferFunctionEditor::onCbLogScaleStateChanged(int state) {
        _logScale = (state & Qt::Checked);
        invalidate();
    }

358

359
}