geometry1dtransferfunctioneditor.cpp 15.5 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-2013, 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
10
// 
schultezub's avatar
schultezub committed
11
// For a full list of authors and contributors, please refer to the file "AUTHORS.txt".
12
// 
13
14
15
16
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 
// except in compliance with the License. You may obtain a copy of the License at
// 
// http://www.apache.org/licenses/LICENSE-2.0
17
// 
18
19
20
21
// Unless required by applicable law or agreed to in writing, software distributed under the 
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
// either express or implied. See the License for the specific language governing permissions 
// and limitations under the License.
22
23
24
// 
// ================================================================================================

schultezub's avatar
schultezub committed
25
#include "geometry1dtransferfunctioneditor.h"
26
27
28

#include "tgt/assert.h"
#include "tgt/shadermanager.h"
29
#include "tgt/glcontextmanager.h"
30
31
32
#include "tgt/qt/qtthreadedcanvas.h"

#include "application/gui/qtcolortools.h"
33
34
#include "application/gui/properties/tfgeometrymanipulator.h"

schultezub's avatar
schultezub committed
35
36
#include "core/classification/geometry1dtransferfunction.h"
#include "core/classification/tfgeometry1d.h"
37
#include "core/datastructures/imagerepresentationlocal.h"
38
39
40
#include "core/properties/transferfunctionproperty.h"
#include "core/tools/opengljobprocessor.h"

41
#include <QCheckBox>
42
43
#include <QGridLayout>
#include <QLabel>
44
45
#include <QPushButton>
#include <QVBoxLayout>
46

schultezub's avatar
schultezub committed
47
namespace campvis {
48

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

schultezub's avatar
schultezub committed
67
    Geometry1DTransferFunctionEditor::~Geometry1DTransferFunctionEditor() {
68
69
        tbb::mutex::scoped_lock lock(_localMutex);

70
        // clearEventListeners and delete former stuff
71
72
73
74
75
76
77
78
        _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
79
        Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
80
        gtf->s_geometryCollectionChanged.disconnect(this);
81
82
83
84
85

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

schultezub's avatar
schultezub committed
88
89
    void Geometry1DTransferFunctionEditor::updateWidgetFromProperty() {
        Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
90
91
        _lblIntensityLeft->setText(QString::number(gtf->getIntensityDomain().x));
        _lblIntensityRight->setText(QString::number(gtf->getIntensityDomain().y));
92
        invalidate();
93
94
    }

schultezub's avatar
schultezub committed
95
96
97
    void Geometry1DTransferFunctionEditor::paint() {
        Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
        const std::vector<TFGeometry1D*>& geometries = gtf->getGeometries();
98

99
        // TODO: get rid of intermediate mode?
100
        glPushAttrib(GL_ALL_ATTRIB_BITS);
101
102
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
103
104
        glViewport(0, 0, _canvas->width(), _canvas->height());

105
106
        glMatrixMode(GL_PROJECTION);
        glPushMatrix();
107
        glOrtho(0, 1 , 0, 1, -1, 1);
108
109
110
        glClearColor(1.f, 1.f, 1.f, 1.f);
        glClear(GL_COLOR_BUFFER_BIT);
        LGL_ERROR;
111
        
112
        // render histogram if existent
113
        const TransferFunctionProperty::IntensityHistogramType* ih = getIntensityHistogram();
114
115
116
117
        if (ih != 0) {
            size_t numBuckets = ih->getNumBuckets(0);
            if (numBuckets > 0) {
                float maxFilling = static_cast<float>(ih->getMaxFilling());
118
119
                if (_logScale)
                    maxFilling = log(maxFilling);
120
121

                float xl = static_cast<float>(0.f) / static_cast<float>(numBuckets);
122
123
124
                float yl = (_logScale 
                    ? log(static_cast<float>(ih->getNumElements(0))) / maxFilling
                    : static_cast<float>(ih->getNumElements(0)) / maxFilling);
125

126

127
128
129
                glBegin(GL_QUADS);
                glColor4f(1.f, .75f, 0.f, .5f);
                for (size_t i = 1; i < numBuckets; ++i) {
130
131
                    float xr = static_cast<float>(i) / static_cast<float>(numBuckets);
                    float yr = (_logScale 
schultezub's avatar
schultezub committed
132
                        ? std::max(0.f, static_cast<float>(log(static_cast<float>(ih->getNumElements(i)))) / maxFilling)
133
134
                        : static_cast<float>(ih->getNumElements(i)) / maxFilling);
                    
135
136
137
138
                    glVertex2f(xl, 0.f);
                    glVertex2f(xl, yl);
                    glVertex2f(xr, yr);
                    glVertex2f(xr, 0.f);
139
                }
140
                glEnd();
141
            }
142
143
        }

144
145
146
147
148
149
150
151
        // render TF geometries
        {
            tbb::mutex::scoped_lock lock(_localMutex);
            for (std::vector<TFGeometry1D*>::const_iterator it = geometries.begin(); it != geometries.end(); ++it) {
                (*it)->renderIntoEditor();
            }
        }

152
153
154
155
        {
            tbb::mutex::scoped_lock lock(_localMutex);

            // render selected geometry
schultezub's avatar
schultezub committed
156
157
158
159
            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();
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
                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();
179
180
181
182
183
184
        }

        LGL_ERROR;
        glPopAttrib();
    }

schultezub's avatar
schultezub committed
185
    void Geometry1DTransferFunctionEditor::sizeChanged(const tgt::ivec2& size) {
186
        tbb::mutex::scoped_lock lock(_localMutex);
187
188
189
        for (std::vector<AbstractTFGeometryManipulator*>::iterator it = _manipulators.begin(); it != _manipulators.end(); ++it) {
            (*it)->setViewportSize(size);
        }
190
191
192
        invalidate();
    }

schultezub's avatar
schultezub committed
193
    void Geometry1DTransferFunctionEditor::mousePressEvent(tgt::MouseEvent* e) {
194
        if (_selectedGeometry != 0 && e->modifiers() & tgt::Event::CTRL) {
schultezub's avatar
schultezub committed
195
            TFGeometry1D* g = _selectedGeometry->getGeometry();
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
            {
                tbb::mutex::scoped_lock lock(_localMutex);

                // add a control point on CTRL+Click
                std::vector<TFGeometry1D::KeyPoint>& kpts = g->getKeyPoints();
                TFGeometry1D::KeyPoint kp(static_cast<float>(e->x()) / static_cast<float>(_canvas->width()), tgt::col4(255));
                std::vector<TFGeometry1D::KeyPoint>::iterator lb = std::upper_bound(kpts.begin(), kpts.end(), kp);
                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);
212
            }
213

214
            updateManipulators();
215
216
217
218
            g->s_changed();
        }
        else {
            _selectedGeometry = 0;
219
            invalidate();
220
221
222
223
            e->ignore();
        }
    }

224
225
226
227
    void Geometry1DTransferFunctionEditor::repaint() {
        invalidate();
    }

schultezub's avatar
schultezub committed
228
    void Geometry1DTransferFunctionEditor::invalidate() {
229
        GLJobProc.enqueueJob(_canvas, makeJobOnHeap(this, &Geometry1DTransferFunctionEditor::paint), OpenGLJobProcessor::PaintJob);
230
231
    }

schultezub's avatar
schultezub committed
232
233
    void Geometry1DTransferFunctionEditor::setupGUI() {
        Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
234
235
236
237
238
239
240
241
242
243
244

        _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);

245
        _canvas = new tgt::QtThreadedCanvas("", tgt::ivec2(256, 128), tgt::GLCanvas::RGBA_BUFFER, 0, false);
246
        tgt::GlContextManager::getRef().registerContextAndInitGlew(_canvas);
247

248
249
250
251
252
253
254
255
256
257
258
        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);

259
260
261
262
263
264
265
266
267
        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()));
268
269
270
271
        _cbLogScale = new QCheckBox(tr("Logarithmic Scale"), this);
        _cbLogScale->setChecked(true);
        buttonLayout->addWidget(_cbLogScale);
        connect(_cbLogScale, SIGNAL(stateChanged(int)), this, SLOT(onCbLogScaleStateChanged(int)));
272
273
274
275
        _layout->setColumnStretch(2, 1);
        _layout->setRowStretch(2, 1);
    }

schultezub's avatar
schultezub committed
276
    void Geometry1DTransferFunctionEditor::updateManipulators() {
277
278
        tbb::mutex::scoped_lock lock(_localMutex);

279
        // clearEventListeners and delete former stuff
280
        _selectedGeometry = 0;
281
        _canvas->getEventHandler()->clearEventListeners();
282
        for (std::vector<AbstractTFGeometryManipulator*>::iterator it = _manipulators.begin(); it != _manipulators.end(); ++it) {
283
284
285
            if (WholeTFGeometryManipulator* tester = dynamic_cast<WholeTFGeometryManipulator*>(*it)) {
            	tester->s_selected.disconnect(this);
            }
286
287
288
289
            delete *it;
        }
        _manipulators.clear();

schultezub's avatar
schultezub committed
290
291
292
        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) {
293
            // Add manipulator for the whole geometry and register it as event handler:
294
            WholeTFGeometryManipulator* wtf = new WholeTFGeometryManipulator(_canvas->getSize(), *git);
295
            _manipulators.push_back(wtf);
296
            _canvas->getEventHandler()->addEventListenerToFront(wtf);
schultezub's avatar
schultezub committed
297
            wtf->s_selected.connect(this, &Geometry1DTransferFunctionEditor::onWholeTFGeometryManipulatorSelected);
298
299

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

306
        _canvas->getEventHandler()->addEventListenerToFront(this);
307
308
    }

schultezub's avatar
schultezub committed
309
    void Geometry1DTransferFunctionEditor::onGeometryCollectionChanged() {
310
311
312
        updateManipulators();
    }

schultezub's avatar
schultezub committed
313
    void Geometry1DTransferFunctionEditor::onWholeTFGeometryManipulatorSelected(WholeTFGeometryManipulator* wtf /* :) */) {
314
        _selectedGeometry = wtf;
315
        invalidate();
316
317
    }

schultezub's avatar
schultezub committed
318
319
320
    void Geometry1DTransferFunctionEditor::onBtnAddGeometryClicked() {
        Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
        gtf->addGeometry(TFGeometry1D::createQuad(tgt::vec2(.4f, .6f), tgt::col4(196), tgt::col4(196)));
321
322
    }

schultezub's avatar
schultezub committed
323
    void Geometry1DTransferFunctionEditor::onBtnRemoveGeometryClicked() {
324
325
326
        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
327
328
            Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
            TFGeometry1D* geometryToRemove = _selectedGeometry->getGeometry();
329
330
331
332
333
334
335
336
337

            {
                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;
                    }
338
                }
339
340
                delete _selectedGeometry;
                _selectedGeometry = 0;
341
            }
342

343
344
345
346
            gtf->removeGeometry(geometryToRemove);
        }
    }

347
348
349
350
351
    void Geometry1DTransferFunctionEditor::onCbLogScaleStateChanged(int state) {
        _logScale = (state & Qt::Checked);
        invalidate();
    }

352

353
}