/*========================================================================= medInria Copyright (c) INRIA 2013 - 2019. All rights reserved. See LICENSE.txt for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. =========================================================================*/ #include "medClutEditorToolBox.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ///////////////////////////////////////////////////////////////// // medClutEditorToolBox // ///////////////////////////////////////////////////////////////// class medClutEditorToolBoxPrivate { public: QAction *newAction; QAction *applyAction; QAction *loadTableAction; QAction *saveTableAction; QAction *toggleDirectUpdateAction; QAction *discreteModeAction; QAction *infoAction; medClutEditorScene *scene; medClutEditorView *view; medClutEditorHistogram *histogram; unsigned int layerForced; bool discreteMode; bool invertLUT; //for the moment, only used in substrateWorkspace medAbstractData *dtk_data; medAbstractView *med_view; QList tables; QString toolboxName; }; medClutEditorToolBox::medClutEditorToolBox(QWidget *parent) : medToolBox(parent) { d = new medClutEditorToolBoxPrivate; d->scene = new medClutEditorScene; d->view = new medClutEditorView( this ); d->view->setScene(d->scene); d->histogram = nullptr; d->med_view = nullptr; d->toolboxName = "Colormap Editor"; d->newAction = new QAction("New table", this); d->loadTableAction = new QAction("Load table", this); d->saveTableAction = new QAction("Save table", this); d->applyAction = new QAction("Apply", this); d->toggleDirectUpdateAction = new QAction("Direct update", this); d->discreteModeAction = new QAction("Discrete Mode", this); d->infoAction = new QAction(QPixmap(":icons/information.png"), tr("About"), this); d->loadTableAction->setEnabled( true ); d->saveTableAction->setEnabled( true ); d->toggleDirectUpdateAction->setCheckable( true ); d->toggleDirectUpdateAction->setChecked( true ); d->discreteModeAction->setCheckable( true ); connect(d->newAction, SIGNAL(triggered()), this, SLOT(onNewTableAction())); connect(d->loadTableAction, SIGNAL(triggered()), this, SLOT(onLoadTableAction())); connect(d->saveTableAction, SIGNAL(triggered()), this, SLOT(onSaveTableAction())); connect(d->applyAction, SIGNAL(triggered()), this, SLOT(onApplyTablesAction())); connect(d->toggleDirectUpdateAction, SIGNAL(triggered()), this, SLOT(onToggleDirectUpdateAction())); connect(d->discreteModeAction, SIGNAL(toggled(bool)), this, SLOT(setDiscreteMode(bool))); connect(d->infoAction, SIGNAL(triggered()), this, SLOT(showInfo())); d->discreteModeAction->setChecked( false ); QWidget* widget = new QWidget(this); QHBoxLayout *layout = new QHBoxLayout; layout->addWidget(d->view); widget->setLayout(layout); this->addWidget(widget); this->setTitle(d->toolboxName); widget->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Minimum); this->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Minimum); //Disables minimizing disconnect(this->header(),SIGNAL(triggered()),this,SLOT(switchMinimize())); //Get back saved LUTs updateSavedTables(); d->discreteMode = false; d->invertLUT = false; d->layerForced = 0; } medClutEditorToolBox::~medClutEditorToolBox(void) { for(medClutEditorTable* table : d->tables) { delete table; } delete d->scene; delete d->view; delete d; } void medClutEditorToolBox::reset() { d->dtk_data = nullptr; d->med_view = nullptr; d->discreteMode = false; d->layerForced = 0; } medClutEditorScene * medClutEditorToolBox::getScene() { return d->scene; } bool medClutEditorToolBox::registered() { return medToolBoxFactory::instance()->registerToolBox(); } void medClutEditorToolBox::setData(medAbstractData *data) { if ( data == nullptr ) { this->deleteTable(); d->dtk_data = data; } if ( data == d->dtk_data ) { return; } if (medAbstractImageData *image = dynamic_cast(data)) { if ( d->histogram != nullptr ) { delete d->histogram; } d->histogram = new medClutEditorHistogram(); this->getScene()->addItem( d->histogram ); int min_range = image->minRangeValue(); int max_range = image->maxRangeValue(); QMap bins; for ( int i = min_range; i <= max_range; ++i ) { qreal count = static_cast< qreal >( image->scalarValueCount( i - min_range ) ); //the histogram (calculated in itkDataImage)'s first value is 0 //otherwise shift of this histogram bins.insert( static_cast< qreal >( i ), count ); } d->histogram->setValues( bins ); this->initializeTable(); d->dtk_data = image; } } void medClutEditorToolBox::setView(medAbstractView *view) { if(!view) { return; } medAbstractImageData *image; if(medAbstractImageView *imageView = qobject_cast(view)) { image = static_cast(imageView->layerData(imageView->currentLayer())); } else { image = static_cast(view->dtkAbstractView::data()); } if( !image || image->identifier()=="vtkDataMesh" ) //no windowing on meshes { return; } this->setData(image); if (view == d->med_view) { return; } d->med_view = view; if (d->med_view != nullptr) { QList scalars; QList colors; // Allows to modif medClutTbx when windowing with dragging on the view // AND used to initialize tbx with the image LUT this->getTransferFunctions( scalars, colors ); medClutEditorTable * table = this->getScene()->table(); table->setTransferFunction( scalars, colors ); } } void medClutEditorToolBox::initializeTable(void) { this->deleteTable(); medClutEditorTable *lut = new medClutEditorTable("Unknown"); this->getScene()->addItem( lut ); connect (lut, SIGNAL(vertexMoving()), this, SLOT(onVertexMoved())); connect (lut, SIGNAL(vertexSet()), this, SLOT(onVertexMoved())); connect (lut, SIGNAL(vertexAdded()), this, SLOT(onVertexMoved())); connect (lut, SIGNAL(vertexRemoved()), this, SLOT(onVertexMoved())); if(d->discreteMode) { lut->setDiscreteMode(true); d->discreteModeAction->setChecked(true); //update GUI } } void medClutEditorToolBox::deleteTable(void) { medClutEditorTable * table; if(!this->getScene()->table()) { return; } while ( (table = this->getScene()->table()) ) { this->getScene()->removeItem( table ); delete table; } } void medClutEditorToolBox::applyTable(medAbstractView* view) { if ( view != nullptr ) { QList scalars; QList colors; medClutEditorTable *clutTable = this->getScene()->table(); clutTable->getTransferFunction(scalars, colors); this->setColorLookupTable(view, scalars, colors); view->viewWidget()->update(); //modifies the interpolation used before rendering (default=linear, set when setting VR), //Neareast-N does not create new values (good for histogram consistency) static_cast(view->backend())->view3D->GetVolumeProperty()->SetInterpolationTypeToNearest(); } } void medClutEditorToolBox::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::RightButton) { event->accept(); QMenu menu("Color Lookup Table", this); menu.setWindowOpacity(0.8); if ( !d->toggleDirectUpdateAction->isChecked() ) { menu.addAction(d->applyAction); } menu.addAction(d->toggleDirectUpdateAction); menu.addAction(d->discreteModeAction); menu.addSeparator(); menu.addAction(d->newAction); menu.addAction(d->loadTableAction); menu.addAction(d->saveTableAction); menu.addAction(d->infoAction); menu.exec(mapFrom(this, QCursor::pos())); //hack to get the rubber band back after right clicking on the view d->view->setDragMode(QGraphicsView::RubberBandDrag); } } void medClutEditorToolBox::onNewTableAction(void) { this->getScene()->table()->deleteAllVertices(); } void medClutEditorToolBox::onVertexMoved(void) { if ( this->getScene()->table() ) { if ( d->toggleDirectUpdateAction->isChecked() ) { this->applyTable(d->med_view); } } } void medClutEditorToolBox::onApplyTablesAction(void) { this->applyTable(d->med_view); } void medClutEditorToolBox::onToggleDirectUpdateAction(void) { if ( d->toggleDirectUpdateAction->isChecked() ) { this->applyTable(d->med_view); } } void medClutEditorToolBox::setDiscreteMode(bool value) { d->discreteMode = value; if(this->getScene()->table()) { this->getScene()->table()->setDiscreteMode(value); // Purely visual changes } this->applyTable(d->med_view); // LUT changes } void medClutEditorToolBox::invertLUT(bool value) { d->invertLUT = value; if(this->getScene()->table()) { this->getScene()->table()->invertLUT(value); } this->applyTable(d->med_view); } void medClutEditorToolBox::onLoadTableAction(void) { updateSavedTables(); QStringList titles; for(medClutEditorTable *table : d->tables) { titles << table->title(); } medLoadLUTDialog dialog (titles,this); // Show it and wait for Ok or Cancel if( dialog.exec() == QDialog::Accepted ) { // We got Ok for(medClutEditorTable *table : d->tables) { if ( table->title() == dialog.textValue()) { this->getScene()->table()->deleteAllVertices(); this->getScene()->removeItem(this->getScene()->table()); this->getScene()->addItem(table); this->getScene()->update(); } } } } void medClutEditorToolBox::onSaveTableAction(void) { if ( medClutEditorTable *table = this->getScene()->table() ) { medSaveLUTDialog dialog (table->title(),this); // Show it and wait for Ok or Cancel if( dialog.exec() == QDialog::Accepted ) { updateSavedTables(); // Copy the current table medClutEditorTable *tableToSave; tableToSave = table; tableToSave->setTitle(dialog.textValue()); // Check if table name already saved int indexIfTableAlreadySaved = -1; int currentIndex = 0; for(medClutEditorTable *savedtable : d->tables) { if (tableToSave->title() == savedtable->title()) { // The file needs to be updated, not append indexIfTableAlreadySaved = currentIndex; break; } currentIndex++; } QList l = d->tables; if (indexIfTableAlreadySaved != -1) { l[indexIfTableAlreadySaved] = tableToSave; // Update } else { l << tableToSave; // Append } medLUTToXMLWriter writer (l); //create file object in an existing storage place medStorage::mkpath(medStorage::dataLocation()); QString fileName = medStorage::dataLocation(); fileName.append("/LUTs.xml"); QFile *file = new QFile(); file->setFileName(fileName); if (file->open(QIODevice::WriteOnly)) { writer.writeFile(file); file->close(); } else { qDebug() << "can't open file " << fileName; } delete file; updateSavedTables(); } } } void medClutEditorToolBox::updateSavedTables() { // Initialize tables for(medClutEditorTable *table : d->tables) { delete table; } d->tables.clear(); // Get table file from file system medXMLToLUTReader reader = medXMLToLUTReader(&d->tables, this->getScene()); QString lutFileName = medStorage::dataLocation(); lutFileName.append("/LUTs.xml"); QFile file(lutFileName); if (file.open(QIODevice::ReadOnly)) { reader.read(&file); file.close(); } } void medClutEditorToolBox::clear() { medTabbedViewContainers *containers = this->getWorkspace()->tabbedViewContainers(); QList containersInTabSelected = containers->containersInTab(containers->currentIndex()); medAbstractView *view = nullptr; for(int i=0; iisSelected()) { view = containersInTabSelected[i]->view(); break; } } this->setView(qobject_cast(view)); } void medClutEditorToolBox::getTransferFunctions ( QList & scalars, QList & colors ) { vtkColorTransferFunction * color = static_cast(d->med_view->backend())->view2D->GetColorTransferFunction(d->layerForced); vtkPiecewiseFunction * opacity = static_cast(d->med_view->backend())->view2D->GetOpacityTransferFunction(d->layerForced); if(!color || !opacity) { return; } if ( color->GetSize() != opacity->GetSize() ) { qWarning() << Q_FUNC_INFO << " sizes of color and opacity transfer " "functions don't match!"; } int size = qMin ( color->GetSize(), opacity->GetSize() ); scalars.clear(); colors.clear(); bool ok = true; for ( int i = 0; i < size; i++ ) { double xrgb[6], xalpha[4]; color->GetNodeValue ( i, xrgb ); opacity->GetNodeValue ( i, xalpha ); if ( xrgb[0] != xalpha[0] ) { ok = false; } scalars << xrgb[0]; QColor c; c.setRgbF ( xrgb[1], xrgb[2], xrgb[3], xalpha[1] ); colors << c; } if ( !ok ) { qWarning() << Q_FUNC_INFO << " x values of color and opacity transfer " "functions don't match!"; } } void medClutEditorToolBox::setColorLookupTable ( medAbstractView *view, QList scalars, QList colors ) { int size = qMin ( scalars.count(),colors.count() ); vtkColorTransferFunction * ctf = vtkColorTransferFunction::New(); vtkPiecewiseFunction * pf = vtkPiecewiseFunction::New(); for ( int i=0; iAddRGBPoint ( scalars.at ( i ), colors.at ( i ).redF(), colors.at ( i ).greenF(), colors.at ( i ).blueF(), d->discreteMode ? !d->invertLUT : 0.5, //midpoint (point between 2 vertices): //either on the right vertex (1), on the left (0), or at the middle (0.5) d->discreteMode ? 1 : 0.0); //sharpness (distribution between 2 vertices): either constant or linear pf->AddPoint ( scalars.at ( i ), colors.at ( i ).alphaF(), d->discreteMode ? !d->invertLUT : 0.5, d->discreteMode ? 1 : 0.0); } double min = scalars.first(); double max = scalars.last(); int n = static_cast< int > ( max - min ) + 1; n = std::max(n, size); double * table = new double[3*n]; double * alphaTable = new double[n]; double * range = ctf->GetRange(); static_cast(view->backend())->view2D->SetColorRange(range); static_cast(view->backend())->view2D->SetColorTransferFunction(ctf); static_cast(view->backend())->view2D->SetOpacityTransferFunction(pf); ctf->GetTable ( min, max, n, table ); ctf->Build(); ctf->Delete(); pf->GetTable ( min,max,n,alphaTable ); pf->Delete(); vtkLookupTable * lut = vtkLookupTable::New(); lut->SetNumberOfTableValues ( n + 2 ); lut->SetTableRange ( min - 1.0, max + 1.0 ); lut->SetTableValue ( 0, 0.0, 0.0, 0.0, 0.0 ); int i, j; for ( i = 0, j = 0; i < n; ++i, j += 3 ) { lut->SetTableValue ( i+1, table[j], table[j+1], table[j+2], alphaTable[i] ); } lut->SetTableValue ( n + 1, 0, 0, 0, 0); //last value of the lut (values above max are black ) static_cast(view->backend())->view2D->SetLookupTable ( lut ); unsigned int layer = static_cast(view)->currentLayer(); static_cast(view->backend())->view3D->SetLookupTable ( lut, layer ); lut->Delete(); delete [] table; delete [] alphaTable; } void medClutEditorToolBox::addVertex(QPointF value, QColor color) { QPointF position = this->getScene()->valueToCoordinate(value); medClutEditorVertex *vertex = new medClutEditorVertex(value, position, color); this->getScene()->table()->addVertex(vertex); this->getScene()->table()->finalizeMoveSelection(); } void medClutEditorToolBox::forceLayer(int layer) { d->layerForced = layer; } void medClutEditorToolBox::showInfo() { QDialog *messageBox = new QDialog(this); messageBox->setWindowTitle(d->toolboxName); QString description = "This histogram allows you to interactively apply a LUT to your image.
"; description += "This LUT can be either discrete or linear."; QTextBrowser* descriptionWidget = new QTextBrowser; descriptionWidget->setHtml(description); QString shortcuts = "Double click: Add a new vertex.
"; shortcuts +=" Delete: Remove the selected vertex.
"; shortcuts +=" a: Select all vertices.
"; shortcuts +=" c: Modify color of the selected vertex.
"; shortcuts +=" +/-: Modify transparency of the LUT representation"; QTextBrowser* shortcutsWidget = new QTextBrowser; shortcutsWidget->setHtml(shortcuts); QFormLayout *layout = new QFormLayout(messageBox); layout->addRow("Description", descriptionWidget); layout->addRow("Shortcuts", shortcutsWidget); messageBox->setLayout(layout); messageBox->show(); }