geometry1dtransferfunctioneditor.cpp 15.7 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
        const tgt::vec2& intensityDomain = gtf->getIntensityDomain();

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

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

                float xl = static_cast<float>(0.f) / static_cast<float>(numBuckets);
                float xr = 0.f;
124
125
126
                float yl = (_logScale 
                    ? log(static_cast<float>(ih->getNumElements(0))) / maxFilling
                    : static_cast<float>(ih->getNumElements(0)) / maxFilling);
127
128
                float yr = 0.f;

129

130
131
132
133
                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);
134
                    yr = (_logScale 
schultezub's avatar
schultezub committed
135
                        ? std::max(0.f, static_cast<float>(log(static_cast<float>(ih->getNumElements(i)))) / maxFilling)
136
137
                        : static_cast<float>(ih->getNumElements(i)) / maxFilling);
                    
138
139
140
141
142
143
144
                    glVertex2f(xl, 0.f);
                    glVertex2f(xl, yl);
                    glVertex2f(xr, yr);
                    glVertex2f(xr, 0.f);

                    xl = xr;
                    yl = yr;
145
                }
146
                glEnd();
147
            }
148
149
        }

150
151
152
153
154
155
156
157
        // render TF geometries
        {
            tbb::mutex::scoped_lock lock(_localMutex);
            for (std::vector<TFGeometry1D*>::const_iterator it = geometries.begin(); it != geometries.end(); ++it) {
                (*it)->renderIntoEditor();
            }
        }

158
159
160
161
        {
            tbb::mutex::scoped_lock lock(_localMutex);

            // render selected geometry
schultezub's avatar
schultezub committed
162
163
164
165
            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();
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
                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();
185
186
187
188
189
190
        }

        LGL_ERROR;
        glPopAttrib();
    }

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

schultezub's avatar
schultezub committed
199
    void Geometry1DTransferFunctionEditor::mousePressEvent(tgt::MouseEvent* e) {
200
        if (_selectedGeometry != 0 && e->modifiers() & tgt::Event::CTRL) {
schultezub's avatar
schultezub committed
201
            TFGeometry1D* g = _selectedGeometry->getGeometry();
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
            {
                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);
218
            }
219

220
            updateManipulators();
221
222
223
224
            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
}