geometry1dtransferfunctioneditor.cpp 15.8 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
8
9
//      Chair for Computer Aided Medical Procedures
//      Technische Universitt Mnchen
//      Boltzmannstr. 3, 85748 Garching b. Mnchen, 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
34
35
36
37

#include "tgt/assert.h"
#include "tgt/shadermanager.h"
#include "tgt/qt/qtcontextmanager.h"
#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
75
76
77
78
79
80
81
82
83
        tbb::mutex::scoped_lock lock(_localMutex);

        // clear and delete former stuff
        _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
        // TODO: this needs to be done, but we can not ensure that GLJobProc is still existant during deconstruction...
87
88
89
        //GLJobProc.deregisterContext(_canvas);
    }

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

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

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

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

116
        // renderIntoEditor TF geometries
schultezub's avatar
schultezub committed
117
        for (std::vector<TFGeometry1D*>::const_iterator it = geometries.begin(); it != geometries.end(); ++it) {
118
            (*it)->renderIntoEditor();
119
120
121
122
123
124
125
126
        }

        // 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());
127
128
                if (_logScale)
                    maxFilling = log(maxFilling);
129
130
131

                float xl = static_cast<float>(0.f) / static_cast<float>(numBuckets);
                float xr = 0.f;
132
133
134
                float yl = (_logScale 
                    ? log(static_cast<float>(ih->getNumElements(0))) / maxFilling
                    : static_cast<float>(ih->getNumElements(0)) / maxFilling);
135
136
                float yr = 0.f;

137

138
139
140
141
                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);
142
143
144
145
                    yr = (_logScale 
                        ? std::max(0.f, log(static_cast<float>(ih->getNumElements(i))) / maxFilling)
                        : static_cast<float>(ih->getNumElements(i)) / maxFilling);
                    
146
147
148
149
150
151
152
                    glVertex2f(xl, 0.f);
                    glVertex2f(xl, yl);
                    glVertex2f(xr, yr);
                    glVertex2f(xr, 0.f);

                    xl = xr;
                    yl = yr;
153
                }
154
                glEnd();
155
            }
156
157
        }

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
        }

        LGL_ERROR;
        glPopAttrib();
189
190

        gtf->unlock();
191
192
    }

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

schultezub's avatar
schultezub committed
201
    void Geometry1DTransferFunctionEditor::mousePressEvent(tgt::MouseEvent* e) {
202
        if (_selectedGeometry != 0 && e->modifiers() & tgt::Event::CTRL) {
schultezub's avatar
schultezub committed
203
            // add a control point on CTRL+Click
schultezub's avatar
schultezub committed
204
205
206
207
            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));
            std::vector<TFGeometry1D::KeyPoint>::const_iterator lb = std::upper_bound(kpts.begin(), kpts.end(), kp);
208
209
210
211
212
213
214
215
216
217
218
219
220
221
            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;
222
            invalidate();
223
224
225
226
            e->ignore();
        }
    }

schultezub's avatar
schultezub committed
227
228
    void Geometry1DTransferFunctionEditor::invalidate() {
        GLJobProc.enqueueJob(_canvas, new CallMemberFuncJob<Geometry1DTransferFunctionEditor>(this, &Geometry1DTransferFunctionEditor::paint), OpenGLJobProcessor::PaintJob);
229
230
    }

schultezub's avatar
schultezub committed
231
232
    void Geometry1DTransferFunctionEditor::setupGUI() {
        Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256

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

        _canvas = CtxtMgr.createContext("tfcanvas", "", tgt::ivec2(256, 128), tgt::GLCanvas::RGBA_BUFFER, 0, false);
        _canvas->doneCurrent();
        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);

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

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

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

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

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

304
        _canvas->getEventHandler()->addListenerToFront(this);
305
306
    }

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

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

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

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

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

341
342
343
344
            gtf->removeGeometry(geometryToRemove);
        }
    }

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

350
351

}