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

geometry1dtransferfunctioneditor.cpp 15.6 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-2015, 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 Universitaet Muenchen
//      Boltzmannstr. 3, 85748 Garching b. Muenchen, 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 29 30
#include "cgt/assert.h"
#include "cgt/shadermanager.h"
#include "cgt/glcontextmanager.h"
#include "cgt/qt/qtthreadedcanvas.h"
31 32

#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
#include "core/properties/transferfunctionproperty.h"

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

schultezub's avatar
schultezub committed
46
namespace campvis {
47

48 49
    Geometry1DTransferFunctionEditor::Geometry1DTransferFunctionEditor(TransferFunctionProperty* prop, Geometry1DTransferFunction* tf, QWidget* parent /*= 0*/)
        : AbstractTransferFunctionEditor(prop, tf, parent)
50
        , _logScale(true)
51 52
        , _layout(0)
        , _canvas(0)
53 54
        , _lblIntensityLeft(0)
        , _lblIntensityRight(0)
55
        , _btnAddGeometry(0)
56 57
        , _btnRemoveGeometry(0)
        , _cbLogScale(0)
58
    {
59
        _selectedGeometry = 0;
60
        setupGUI();
61

schultezub's avatar
schultezub committed
62
        tf->s_geometryCollectionChanged.connect(this, &Geometry1DTransferFunctionEditor::onGeometryCollectionChanged);
63 64
        tf->s_aboutToBeDeleted.connect(this, &Geometry1DTransferFunctionEditor::onTfAboutToBeDeleted);

65
        updateManipulators();
66
        setEventTypes(cgt::Event::MOUSEPRESSEVENT);
67 68

        connect(this, &Geometry1DTransferFunctionEditor::s_invalidated, this, &Geometry1DTransferFunctionEditor::onInvalidated);
69 70
    }

schultezub's avatar
schultezub committed
71
    Geometry1DTransferFunctionEditor::~Geometry1DTransferFunctionEditor() {
72
        disconnectFromTf();
73

74 75
        if (cgt::GlContextManager::isInited())
            cgt::GlContextManager::getRef().removeContext(_canvas);
76 77
    }

schultezub's avatar
schultezub committed
78 79
    void Geometry1DTransferFunctionEditor::updateWidgetFromProperty() {
        Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
80 81
        _lblIntensityLeft->setText(QString::number(gtf->getIntensityDomain().x));
        _lblIntensityRight->setText(QString::number(gtf->getIntensityDomain().y));
82
        emit s_invalidated();
83 84
    }

schultezub's avatar
schultezub committed
85 86 87
    void Geometry1DTransferFunctionEditor::paint() {
        Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
        const std::vector<TFGeometry1D*>& geometries = gtf->getGeometries();
88

89
        // TODO: get rid of intermediate mode?
90
        glPushAttrib(GL_ALL_ATTRIB_BITS);
91 92
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
93 94
        glViewport(0, 0, _canvas->width(), _canvas->height());

95 96
        glMatrixMode(GL_PROJECTION);
        glPushMatrix();
97
        glOrtho(0, 1 , 0, 1, -1, 1);
98 99 100
        glClearColor(1.f, 1.f, 1.f, 1.f);
        glClear(GL_COLOR_BUFFER_BIT);
        LGL_ERROR;
101
        
102
        // render histogram if existent
103
        const TransferFunctionProperty::IntensityHistogramType* ih = getIntensityHistogram();
104 105 106 107
        if (ih != 0) {
            size_t numBuckets = ih->getNumBuckets(0);
            if (numBuckets > 0) {
                float maxFilling = static_cast<float>(ih->getMaxFilling());
108 109
                if (_logScale)
                    maxFilling = log(maxFilling);
110 111

                float xl = static_cast<float>(0.f) / static_cast<float>(numBuckets);
112 113 114
                float yl = (_logScale 
                    ? log(static_cast<float>(ih->getNumElements(0))) / maxFilling
                    : static_cast<float>(ih->getNumElements(0)) / maxFilling);
115

116

117 118 119
                glBegin(GL_QUADS);
                glColor4f(1.f, .75f, 0.f, .5f);
                for (size_t i = 1; i < numBuckets; ++i) {
120 121
                    float xr = static_cast<float>(i) / static_cast<float>(numBuckets);
                    float yr = (_logScale 
schultezub's avatar
schultezub committed
122
                        ? std::max(0.f, static_cast<float>(log(static_cast<float>(ih->getNumElements(i)))) / maxFilling)
123 124
                        : static_cast<float>(ih->getNumElements(i)) / maxFilling);
                    
125 126 127 128
                    glVertex2f(xl, 0.f);
                    glVertex2f(xl, yl);
                    glVertex2f(xr, yr);
                    glVertex2f(xr, 0.f);
129 130 131

                    xl = xr;
                    yl = yr;
132
                }
133
                glEnd();
134
            }
135 136
        }

137 138 139 140 141 142 143 144
        // render TF geometries
        {
            tbb::mutex::scoped_lock lock(_localMutex);
            for (std::vector<TFGeometry1D*>::const_iterator it = geometries.begin(); it != geometries.end(); ++it) {
                (*it)->renderIntoEditor();
            }
        }

145 146 147 148
        {
            tbb::mutex::scoped_lock lock(_localMutex);

            // render selected geometry
schultezub's avatar
schultezub committed
149 150 151
            WholeTFGeometryManipulator* selectedGeometry = _selectedGeometry;
            if (selectedGeometry != 0) {
                // the copy is deliberate for improved thread safety (the whole design is a little messy here...)
152
                std::vector<cgt::vec2> helperPoints = selectedGeometry->getHelperPoints();
153 154 155 156
                glColor4ub(0, 0, 0, 196);
                glEnable(GL_LINE_STIPPLE);
                glLineStipple(1, 0xFAFA);
                glBegin(GL_LINE_LOOP);
157
                for (std::vector<cgt::vec2>::const_iterator it = helperPoints.begin(); it != helperPoints.end(); ++it)
158 159 160 161 162 163 164 165 166 167 168 169 170 171
                    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();
172 173 174 175 176 177
        }

        LGL_ERROR;
        glPopAttrib();
    }

178
    void Geometry1DTransferFunctionEditor::sizeChanged(const cgt::ivec2& size) {
179 180 181 182 183
        {
            tbb::mutex::scoped_lock lock(_localMutex);
            for (std::vector<AbstractTFGeometryManipulator*>::iterator it = _manipulators.begin(); it != _manipulators.end(); ++it) {
                (*it)->setViewportSize(size);
            }
184
        }
185
        emit s_invalidated();
186 187
    }

188 189
    void Geometry1DTransferFunctionEditor::mousePressEvent(cgt::MouseEvent* e) {
        if (_selectedGeometry != 0 && e->modifiers() & cgt::Event::CTRL) {
schultezub's avatar
schultezub committed
190
            TFGeometry1D* g = _selectedGeometry->getGeometry();
191 192 193
            {
                tbb::mutex::scoped_lock lock(_localMutex);

194
                float pos = static_cast<float>(e->x()) / static_cast<float>(_canvas->width());
195
                float alpha = cgt::clamp(static_cast<float>(_canvas->height() - e->y()) / static_cast<float>(_canvas->height()), 0.f, 1.f);
196 197
                
                g->addKeyPoint(pos, alpha);
198
            }
199

200
            updateManipulators();
201
            g->s_changed.emitSignal();
202 203 204
        }
        else {
            _selectedGeometry = 0;
205
            emit s_invalidated();
206 207 208 209
            e->ignore();
        }
    }

210
    void Geometry1DTransferFunctionEditor::repaint() {
211
        emit s_invalidated();
212 213
    }

214
    void Geometry1DTransferFunctionEditor::onInvalidated() {
215
        // TODO: check, whether this should be done in an extra thread
216
        cgt::GLContextScopedLock lock(_canvas);
217
        paint();
218 219
    }

schultezub's avatar
schultezub committed
220 221
    void Geometry1DTransferFunctionEditor::setupGUI() {
        Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
222 223 224 225 226 227 228 229 230 231 232

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

233
        _canvas = new cgt::QtThreadedCanvas("", cgt::ivec2(256, 128), cgt::GLCanvas::RGBA_BUFFER, 0, false);
234 235
        GLCtxtMgr.registerContextAndInitGlew(_canvas, "Geometry1DTransferFunctionEditor");
        GLCtxtMgr.releaseContext(_canvas, false);
236

237
        _canvas->setPainter(this);
238 239 240 241 242 243 244 245 246
        _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);

247
        QVBoxLayout* buttonLayout = new QVBoxLayout();
248 249 250 251 252 253 254 255
        _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()));
256 257 258 259
        _cbLogScale = new QCheckBox(tr("Logarithmic Scale"), this);
        _cbLogScale->setChecked(true);
        buttonLayout->addWidget(_cbLogScale);
        connect(_cbLogScale, SIGNAL(stateChanged(int)), this, SLOT(onCbLogScaleStateChanged(int)));
260 261 262 263
        _layout->setColumnStretch(2, 1);
        _layout->setRowStretch(2, 1);
    }

schultezub's avatar
schultezub committed
264
    void Geometry1DTransferFunctionEditor::updateManipulators() {
265 266
        tbb::mutex::scoped_lock lock(_localMutex);

267
        // clearEventListeners and delete former stuff
268
        _selectedGeometry = 0;
269
        _canvas->getEventHandler()->clearEventListeners();
270
        for (std::vector<AbstractTFGeometryManipulator*>::iterator it = _manipulators.begin(); it != _manipulators.end(); ++it) {
271
            if (WholeTFGeometryManipulator* tester = dynamic_cast<WholeTFGeometryManipulator*>(*it)) {
272
                tester->s_selected.disconnect(this);
273
            }
274 275 276 277
            delete *it;
        }
        _manipulators.clear();

schultezub's avatar
schultezub committed
278 279 280
        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) {
281
            // Add manipulator for the whole geometry and register it as event handler:
282
            WholeTFGeometryManipulator* wtf = new WholeTFGeometryManipulator(_canvas->getSize(), *git);
283
            _manipulators.push_back(wtf);
284
            _canvas->getEventHandler()->addEventListenerToFront(wtf);
schultezub's avatar
schultezub committed
285
            wtf->s_selected.connect(this, &Geometry1DTransferFunctionEditor::onWholeTFGeometryManipulatorSelected);
286 287

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

294
        _canvas->getEventHandler()->addEventListenerToFront(this);
295 296
    }

schultezub's avatar
schultezub committed
297
    void Geometry1DTransferFunctionEditor::onGeometryCollectionChanged() {
298 299 300
        updateManipulators();
    }

schultezub's avatar
schultezub committed
301
    void Geometry1DTransferFunctionEditor::onWholeTFGeometryManipulatorSelected(WholeTFGeometryManipulator* wtf /* :) */) {
302
        _selectedGeometry = wtf;
303
        emit s_invalidated();
304 305
    }

schultezub's avatar
schultezub committed
306 307
    void Geometry1DTransferFunctionEditor::onBtnAddGeometryClicked() {
        Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
308
        gtf->addGeometry(TFGeometry1D::createQuad(cgt::vec2(.4f, .6f), cgt::col4(196), cgt::col4(196)));
309 310
    }

schultezub's avatar
schultezub committed
311
    void Geometry1DTransferFunctionEditor::onBtnRemoveGeometryClicked() {
312 313 314
        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
315 316
            Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
            TFGeometry1D* geometryToRemove = _selectedGeometry->getGeometry();
317 318 319 320 321 322 323 324 325

            {
                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;
                    }
326
                }
327 328
                delete _selectedGeometry;
                _selectedGeometry = 0;
329
            }
330

331 332 333 334
            gtf->removeGeometry(geometryToRemove);
        }
    }

335 336
    void Geometry1DTransferFunctionEditor::onCbLogScaleStateChanged(int state) {
        _logScale = (state & Qt::Checked);
337
        emit s_invalidated();
338 339
    }

340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
    void Geometry1DTransferFunctionEditor::onTfAboutToBeDeleted() {
        disconnectFromTf();
    }

    void Geometry1DTransferFunctionEditor::disconnectFromTf() {
        tbb::mutex::scoped_lock lock(_localMutex);

        // clearEventListeners 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;
        }
        _manipulators.clear();

        Geometry1DTransferFunction* gtf = static_cast<Geometry1DTransferFunction*>(_transferFunction);
        if (gtf != nullptr) {
            gtf->s_geometryCollectionChanged.disconnect(this);
            gtf->s_aboutToBeDeleted.disconnect(this);
            _transferFunction->s_changed.disconnect(this);
            _transferFunction = nullptr;
        }
    }

366

367
}