mirror of https://github.com/CGAL/cgal
380 lines
11 KiB
C++
380 lines
11 KiB
C++
#include <CGAL/Image_3.h>
|
|
|
|
#include "Volume_plane.h"
|
|
#include "Volume_plane_thread.h"
|
|
#include "Volume_plane_intersection.h"
|
|
|
|
#include "Scene_segmented_image_item.h"
|
|
#include <CGAL_demo/Plugin_helper.h>
|
|
#include <CGAL_demo/Plugin_interface.h>
|
|
#include <CGAL_demo/Messages_interface.h>
|
|
#include <CGAL_demo/Scene_interface.h>
|
|
#include <CGAL_demo/Scene_item.h>
|
|
#include <CGAL_demo/Viewer.h>
|
|
#include "MainWindow.h"
|
|
|
|
#include <QAction>
|
|
#include <QMenu>
|
|
#include <QList>
|
|
#include <QInputDialog>
|
|
#include <QSlider>
|
|
#include <QLabel>
|
|
#include <QHBoxLayout>
|
|
#include <QVBoxLayout>
|
|
#include <QDockWidget>
|
|
#include <QMainWindow>
|
|
#include <QMessageBox>
|
|
#include <QString>
|
|
#include <QFontMetrics>
|
|
|
|
#include <cassert>
|
|
#include <iostream>
|
|
|
|
#include <boost/type_traits.hpp>
|
|
#include <boost/optional.hpp>
|
|
|
|
// Covariant return types don't work for scalar types and we cannot
|
|
// have templates here, hence this unfortunate hack.
|
|
|
|
// The input float value we are reading is always in
|
|
// 0..1 and min_max is the range it came from.
|
|
struct IntConverter {
|
|
std::pair<int, int> min_max;
|
|
|
|
int operator()(float f) {
|
|
float s = f * (min_max.second - min_max.first);
|
|
return s + min_max.first;
|
|
}
|
|
};
|
|
|
|
struct DoubleConverter {
|
|
std::pair<float, float> min_max;
|
|
|
|
float operator()(float f) {
|
|
float s = f * (min_max.second - min_max.first);
|
|
return s + min_max.first;
|
|
}
|
|
};
|
|
|
|
class PixelReader : public QObject
|
|
{
|
|
Q_OBJECT
|
|
public slots:
|
|
void update(const QPoint& p) {
|
|
getPixel(p);
|
|
}
|
|
signals:
|
|
void x(int);
|
|
|
|
public:
|
|
void setIC(const IntConverter& x) { ic = x; fc = boost::optional<DoubleConverter>(); }
|
|
void setFC(const DoubleConverter& x) { fc = x; ic = boost::optional<IntConverter>(); }
|
|
|
|
private:
|
|
boost::optional<IntConverter> ic;
|
|
boost::optional<DoubleConverter> fc;
|
|
|
|
void getPixel(const QPoint& e) {
|
|
float data[3];
|
|
int vp[4];
|
|
glGetIntegerv(GL_VIEWPORT, vp);
|
|
glReadPixels(e.x(), vp[3] - e.y(), 1, 1, GL_RGB, GL_FLOAT, data);
|
|
|
|
if(fc) {
|
|
emit x( (*fc)(data[0]) );
|
|
} else if(ic) {
|
|
emit x( (*ic)(data[0]) );
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
class Plane_slider : public QSlider
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
Plane_slider(const qglviewer::Vec& v, int id, Scene_interface* scene,
|
|
qglviewer::ManipulatedFrame* frame, Qt::Orientation ori, QWidget* widget)
|
|
: QSlider(ori, widget), v(v), id(id), scene(scene), frame(frame) {
|
|
this->setTracking(true);
|
|
connect(frame, SIGNAL(manipulated()), this, SLOT(updateValue()));
|
|
}
|
|
|
|
public:
|
|
void sliderChange(SliderChange c) {
|
|
QSlider::sliderChange(c);
|
|
if(c == SliderValueChange) {
|
|
qglviewer::Vec v2 = v * (this->value() / scale);
|
|
frame->setTranslationWithConstraint(v2);
|
|
scene->itemChanged(id);
|
|
}
|
|
|
|
emit realChange(this->value() / scale);
|
|
}
|
|
|
|
public slots:
|
|
void updateValue() {
|
|
float a, b, c;
|
|
frame->getPosition(a, b, c);
|
|
float sum1 = a + b + c;
|
|
float sum2 = v.x + v.y + v.z;
|
|
sum1 /= sum2;
|
|
setValue(sum1 * scale);
|
|
}
|
|
|
|
signals:
|
|
void realChange(int);
|
|
|
|
private:
|
|
const static unsigned int scale;
|
|
|
|
qglviewer::Vec v;
|
|
int id;
|
|
Scene_interface* scene;
|
|
qglviewer::ManipulatedFrame* frame;
|
|
};
|
|
|
|
const unsigned int Plane_slider::scale = 100;
|
|
|
|
class Volume_plane_plugin :
|
|
public QObject,
|
|
public Plugin_interface
|
|
{
|
|
Q_OBJECT
|
|
Q_INTERFACES(Plugin_interface)
|
|
public:
|
|
Volume_plane_plugin() : planeSwitch(NULL), sc(NULL), mw(NULL)
|
|
{
|
|
}
|
|
|
|
virtual void init(QMainWindow* mw, Scene_interface* sc) {
|
|
assert(mw != NULL);
|
|
assert(sc != NULL);
|
|
this->sc = sc;
|
|
this->mw = mw;
|
|
|
|
QList<QMenu*> menus = mw->findChildren<QMenu*>();
|
|
|
|
planeSwitch = new QAction(mw);
|
|
planeSwitch->setText("Add Volume Planes");
|
|
|
|
connect(planeSwitch, SIGNAL(triggered()), this, SLOT(selectPlanes()));
|
|
|
|
// evil
|
|
MainWindow* mwTmp;
|
|
if( !(mwTmp = dynamic_cast<MainWindow*>(mw)) ) {
|
|
std::cerr << "Volume_planes_plugin cannot init mousegrabber" << std::endl;
|
|
}
|
|
Viewer* v = mwTmp->getViewer();
|
|
connect(v, SIGNAL(pointSelected(QPoint)), &pxr_, SLOT(update(QPoint)));
|
|
|
|
createOrGetDockLayout();
|
|
}
|
|
|
|
virtual QList<QAction*> actions() const {
|
|
return QList<QAction*>() << planeSwitch;
|
|
}
|
|
|
|
public slots:
|
|
void selectPlanes() {
|
|
std::vector< Scene_segmented_image_item* > seg_items;
|
|
Scene_segmented_image_item* seg_img = NULL;
|
|
|
|
for(unsigned int i = 0; i < sc->numberOfEntries(); ++i) {
|
|
Scene_segmented_image_item* tmp = qobject_cast<Scene_segmented_image_item*>(sc->item(i));
|
|
if(tmp != NULL)
|
|
seg_items.push_back(tmp);
|
|
}
|
|
|
|
if(seg_items.empty()) {
|
|
QMessageBox::warning(mw, tr("No suitable item found"), tr("Load an inrimage or hdr file to enable Volume Planes."));
|
|
return;
|
|
} else {
|
|
QList<QString> items;
|
|
for(std::vector< Scene_segmented_image_item* >::const_iterator it = seg_items.begin();
|
|
it != seg_items.end(); ++it) {
|
|
items << (*it)->name();
|
|
}
|
|
|
|
bool ok;
|
|
QString selected = QInputDialog::getItem(mw, tr("Select a dataset:"), tr("Items"), items, 0, false, &ok);
|
|
|
|
if(!ok || selected.isEmpty())
|
|
return;
|
|
|
|
for(std::vector< Scene_segmented_image_item*>::const_iterator it = seg_items.begin();
|
|
it != seg_items.end(); ++it) {
|
|
if(selected == (*it)->name())
|
|
seg_img = *it;
|
|
}
|
|
}
|
|
|
|
if(!(seg_img == NULL)) {
|
|
const CGAL::Image_3* img = seg_img->image();
|
|
CGAL_IMAGE_IO_CASE(img->image(), this->launchAdders<Word>(img, seg_img->name()))
|
|
|
|
Volume_plane_intersection* i = new Volume_plane_intersection(img->xdim() * img->vx(),
|
|
img->ydim() * img->vy(),
|
|
img->zdim() * img->vz());
|
|
this->intersectionId = sc->addItem(i);
|
|
} else {
|
|
QMessageBox::warning(mw, tr("Something went wrong"), tr("Selected a suitable Object but couldn't get an image pointer."));
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
void addVP(Volume_plane_thread* thread) {
|
|
Volume_plane_interface* plane = thread->getItem();
|
|
plane->init();
|
|
|
|
// add the interface for this Volume_plane
|
|
int id = sc->addItem(plane);
|
|
|
|
QLayout* layout = createOrGetDockLayout();
|
|
|
|
QWidget* controls = new QWidget;
|
|
QHBoxLayout* box = new QHBoxLayout(controls);
|
|
layout->addWidget(controls);
|
|
|
|
QLabel* label = new QLabel(controls);
|
|
label->setText(plane->name());
|
|
|
|
QLabel* cubeLabel = new QLabel(controls);
|
|
cubeLabel->setNum(static_cast<int>(plane->getCurrentCube()));
|
|
|
|
// Find the right width for the label to accommodate at least 9999
|
|
QFontMetrics metric = cubeLabel->fontMetrics();
|
|
cubeLabel->setFixedWidth(metric.width(QString("9999")));
|
|
|
|
QSlider* slider = new Plane_slider(plane->translationVector(), id, sc, plane->manipulatedFrame(),
|
|
Qt::Horizontal, controls);
|
|
slider->setRange(0, (plane->cDim() - 1) * 100);
|
|
|
|
connect(slider, SIGNAL(realChange(int)), cubeLabel, SLOT(setNum(int)));
|
|
connect(plane, SIGNAL(manipulated(int)), cubeLabel, SLOT(setNum(int)));
|
|
|
|
box->addWidget(label);
|
|
box->addWidget(slider);
|
|
box->addWidget(cubeLabel);
|
|
|
|
connect(plane, SIGNAL(aboutToBeDestroyed()), controls, SLOT(deleteLater()));
|
|
|
|
std::vector<Volume_plane_thread*>::iterator it = std::find(threads.begin(), threads.end(), thread);
|
|
|
|
// this slot has been connected to a thread that hasn't been
|
|
// registered here.
|
|
assert(it != threads.end());
|
|
delete *it;
|
|
threads.erase(it);
|
|
|
|
Volume_plane_intersection* intersection = dynamic_cast<Volume_plane_intersection*>(sc->item(intersectionId));
|
|
if(!intersection) {
|
|
// the intersection is gone before it was initialized
|
|
return;
|
|
}
|
|
// FIXME downcasting mode
|
|
// FIXME this will bug if two volume planes are generated simultaneously by the plugin
|
|
if(Volume_plane<x_tag>* p = dynamic_cast< Volume_plane<x_tag>* >(plane)) {
|
|
intersection->setX(p);
|
|
} else if(Volume_plane<y_tag>* p = dynamic_cast< Volume_plane<y_tag>* >(plane)) {
|
|
intersection->setY(p);
|
|
} else if(Volume_plane<z_tag>* p = dynamic_cast< Volume_plane<z_tag>* >(plane)) {
|
|
intersection->setZ(p);
|
|
}
|
|
|
|
connect(plane, SIGNAL(planeDestructionIncoming(Volume_plane_interface*)),
|
|
intersection, SLOT(planeRemoved(Volume_plane_interface*)));
|
|
}
|
|
|
|
private:
|
|
QAction* planeSwitch;
|
|
Scene_interface* sc;
|
|
QMainWindow* mw;
|
|
PixelReader pxr_;
|
|
|
|
std::vector<Volume_plane_thread*> threads;
|
|
unsigned int intersectionId;
|
|
|
|
QLayout* createOrGetDockLayout() {
|
|
QLayout* layout = NULL;
|
|
QDockWidget* controlDockWidget = mw->findChild<QDockWidget*>("volumePlanesControl");
|
|
if(!controlDockWidget) {
|
|
controlDockWidget = new QDockWidget(mw);
|
|
controlDockWidget->setObjectName("volumePlanesControl");
|
|
QWidget* content = new QWidget(controlDockWidget);
|
|
layout = new QVBoxLayout(content);
|
|
layout->setObjectName("vpSliderLayout");
|
|
controlDockWidget->setWindowTitle("Control Widget");
|
|
mw->addDockWidget(Qt::LeftDockWidgetArea, controlDockWidget);
|
|
|
|
QWidget* vlabels = new QWidget(content);
|
|
layout->addWidget(vlabels);
|
|
QHBoxLayout* vbox = new QHBoxLayout(vlabels);
|
|
vbox->setAlignment(Qt::AlignJustify);
|
|
|
|
QLabel* text = new QLabel(vlabels);
|
|
text->setText("Isovalue at point:");
|
|
QLabel* x = new QLabel(vlabels);
|
|
|
|
connect(&pxr_, SIGNAL(x(int)), x, SLOT(setNum(int)));
|
|
|
|
vbox->addWidget(text); vbox->addWidget(x);
|
|
controlDockWidget->setWidget(content);
|
|
} else {
|
|
layout = controlDockWidget->findChild<QLayout*>("vpSliderLayout");
|
|
}
|
|
|
|
return layout;
|
|
}
|
|
|
|
template<typename Word>
|
|
void launchAdders(const CGAL::Image_3* img, const QString& name) {
|
|
const Word* begin = (const Word*)img->data();
|
|
const Word* end = (const Word*)img->data() + img->size();
|
|
|
|
std::pair<Word, Word> minmax = std::make_pair(*std::min_element(begin, end), *std::max_element(begin, end));
|
|
|
|
Clamp_to_one_zero_range clamper = { minmax };
|
|
|
|
switchReaderConverter< Word >(minmax);
|
|
|
|
threads.push_back(new X_plane_thread<Word>(img, clamper, name));
|
|
connect(threads.back(), SIGNAL(finished(Volume_plane_thread*)), this, SLOT(addVP(Volume_plane_thread*)));
|
|
threads.back()->start();
|
|
|
|
threads.push_back(new Y_plane_thread<Word>(img, clamper, name));
|
|
connect(threads.back(), SIGNAL(finished(Volume_plane_thread*)), this, SLOT(addVP(Volume_plane_thread*)));
|
|
threads.back()->start();
|
|
|
|
threads.push_back(new Z_plane_thread<Word>(img, clamper, name));
|
|
connect(threads.back(), SIGNAL(finished(Volume_plane_thread*)), this, SLOT(addVP(Volume_plane_thread*)));
|
|
threads.back()->start();
|
|
|
|
}
|
|
|
|
template<typename T>
|
|
void switchReaderConverter(std::pair<T, T> minmax) {
|
|
switchReaderConverter(minmax, typename boost::is_integral<T>::type());
|
|
}
|
|
|
|
template<typename T>
|
|
void switchReaderConverter(std::pair<T, T> minmax, boost::true_type) {
|
|
// IntConverter
|
|
IntConverter x = { minmax }; pxr_.setIC(x);
|
|
}
|
|
|
|
template<typename T>
|
|
void switchReaderConverter(std::pair<T, T> minmax, boost::false_type) {
|
|
// IntConverter
|
|
DoubleConverter x = { minmax }; pxr_.setFC(x);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
Q_EXPORT_PLUGIN2(Volume_plane_plugin, Volume_plane_plugin)
|
|
|
|
#include "Volume_planes_plugin.moc"
|