mirror of https://github.com/CGAL/cgal
1563 lines
47 KiB
C++
1563 lines
47 KiB
C++
#ifdef _MSC_VER
|
|
# pragma warning(disable:4244) // conversion with loss of data
|
|
# pragma warning(disable:4996) // boost_1_65_1\boost/iostreams/positioning.hpp(96):
|
|
// warning C4996: 'std::fpos<_Mbstatet>::seekpos': warning STL4019:
|
|
// The member std::fpos::seekpos() is non-Standard
|
|
#endif
|
|
|
|
#include "Volume_plane.h"
|
|
#include "Volume_plane_thread.h"
|
|
#include "Volume_plane_intersection.h"
|
|
#include "Messages_interface.h"
|
|
#include "config.h"
|
|
#include "Scene_image_item.h"
|
|
#include "Image_type.h"
|
|
#include "ui_Image_res_dialog.h"
|
|
|
|
#include <CGAL/Image_3.h>
|
|
#include <CGAL/ImageIO.h>
|
|
#include <CGAL/SEP_to_ImageIO.h>
|
|
#include <CGAL/Three/Polyhedron_demo_plugin_helper.h>
|
|
#include <CGAL/Three/Polyhedron_demo_plugin_interface.h>
|
|
#include <CGAL/Three/Scene_interface.h>
|
|
#include <CGAL/Three/Scene_item.h>
|
|
#include <CGAL/Three/Scene_group_item.h>
|
|
#include <CGAL/Three/Three.h>
|
|
#include <CGAL/Three/Viewer_interface.h>
|
|
#include <CGAL/config.h>
|
|
#include <CGAL/use.h>
|
|
#include <CGAL/IO/helpers.h>
|
|
#include <QAction>
|
|
#include <QMenu>
|
|
#include <QMouseEvent>
|
|
#include <QList>
|
|
#include <QInputDialog>
|
|
#include <QSlider>
|
|
#include <QLabel>
|
|
#include <QLineEdit>
|
|
#include <QHBoxLayout>
|
|
#include <QVBoxLayout>
|
|
#include <QDockWidget>
|
|
#include <QMainWindow>
|
|
#include <QMessageBox>
|
|
#include <QString>
|
|
#include <QFontMetrics>
|
|
#include <QFileDialog>
|
|
#include <QPushButton>
|
|
|
|
#include <QSettings>
|
|
#include <QUrl>
|
|
#include "Raw_image_dialog.h"
|
|
#include <CGAL/Three/Polyhedron_demo_io_plugin_interface.h>
|
|
|
|
#ifdef CGAL_USE_VTK
|
|
#include <CGAL/IO/read_vtk_image_data.h>
|
|
|
|
#include <vtkNew.h>
|
|
#include <vtkSmartPointer.h>
|
|
#include <vtkStringArray.h>
|
|
#include <vtkImageData.h>
|
|
#include <vtkXMLImageDataReader.h>
|
|
#include <vtkDICOMImageReader.h>
|
|
#include <vtkBMPReader.h>
|
|
#include <vtkNIFTIImageReader.h>
|
|
#include <vtkImageReader.h>
|
|
#include <vtkImageGaussianSmooth.h>
|
|
#include <vtkDemandDrivenPipeline.h>
|
|
#include <vtkNrrdReader.h>
|
|
#endif
|
|
|
|
#include <CGAL/Three/Three.h>
|
|
|
|
#include <boost/type_traits.hpp>
|
|
#include <optional>
|
|
#include <boost/filesystem.hpp>
|
|
|
|
#include <cassert>
|
|
#include <cstdlib>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <locale>
|
|
|
|
template <class vtkReader>
|
|
bool
|
|
load_vtk_file(QFileInfo fileinfo, Image* image)
|
|
{
|
|
#ifdef CGAL_USE_VTK
|
|
vtkNew<vtkReader> reader;
|
|
reader->SetFileName(fileinfo.filePath().toUtf8());
|
|
reader->Update();
|
|
auto vtk_image = reader->GetOutput();
|
|
vtk_image->Print(std::cerr);
|
|
*image = CGAL::IO::read_vtk_image_data(vtk_image); // copy the image data
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
// 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 * float((min_max.second - min_max.first));
|
|
//approximate instead of just floor.
|
|
if (s - floor(s) >= 0.5)
|
|
return int(s)+1 + min_max.first;
|
|
else
|
|
return s + float(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 Q_SLOTS:
|
|
void update(const QMouseEvent *e) { getPixel(e->pos()); }
|
|
|
|
Q_SIGNALS:
|
|
void x(QString);
|
|
|
|
public:
|
|
void setIC(const IntConverter& x) { ic = x; fc = std::optional<DoubleConverter>(); }
|
|
void setFC(const DoubleConverter& x) { fc = x; ic = std::optional<IntConverter>(); }
|
|
void setViewer(Viewer_interface* viewer) { this->viewer = viewer; }
|
|
|
|
private:
|
|
std::optional<IntConverter> ic;
|
|
std::optional<DoubleConverter> fc;
|
|
Viewer_interface* viewer;
|
|
void getPixel(const QPoint& e)
|
|
{
|
|
const auto data = read_pixel_as_float_rgb(e, viewer, viewer->camera());
|
|
if(fc) {
|
|
Q_EMIT x(QString::number((*fc)(data[0]), 'f', 6 ));
|
|
} else if(ic) {
|
|
Q_EMIT x( QString::number((*ic)(data[0]) ));
|
|
}
|
|
}
|
|
};
|
|
|
|
class Plane_slider
|
|
: public QSlider
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
Plane_slider(const CGAL::qglviewer::Vec& v,
|
|
int id,
|
|
Scene_interface* scene,
|
|
CGAL::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(updateCutPlane()));
|
|
}
|
|
|
|
public Q_SLOTS:
|
|
void updateCutPlane()
|
|
{
|
|
ready_to_cut = true;
|
|
QTimer::singleShot(0,this,SLOT(updateValue()));
|
|
}
|
|
|
|
void setFramePosition()
|
|
{
|
|
if(!ready_to_move)
|
|
return;
|
|
|
|
const CGAL::qglviewer::Vec offset = Three::mainViewer()->offset();
|
|
CGAL::qglviewer::Vec v2 = v * (this->value() / scale);
|
|
v2 += offset;
|
|
frame->setTranslationWithConstraint(v2);
|
|
scene->itemChanged(id);
|
|
|
|
Q_EMIT realChange(this->value() / scale);
|
|
ready_to_move = false;
|
|
}
|
|
|
|
void updateValue()
|
|
{
|
|
if(!ready_to_cut)
|
|
return;
|
|
|
|
typedef qreal qglviewer_real;
|
|
qglviewer_real a, b, c;
|
|
|
|
frame->getPosition(a, b, c);
|
|
const CGAL::qglviewer::Vec offset = Three::mainViewer()->offset();
|
|
a -= offset.x;
|
|
b -= offset.y;
|
|
c -= offset.z;
|
|
float sum1 = float(a + b + c);
|
|
float sum2 = float(v.x + v.y + v.z);
|
|
sum1 /= sum2;
|
|
setValue(sum1 * float(scale));
|
|
ready_to_cut = false;
|
|
}
|
|
|
|
void updateFramePosition()
|
|
{
|
|
ready_to_move = true;
|
|
QTimer::singleShot(0,this,SLOT(setFramePosition()));
|
|
}
|
|
|
|
unsigned int getScale() const { return scale; }
|
|
|
|
Q_SIGNALS:
|
|
void realChange(int);
|
|
|
|
private:
|
|
static const unsigned int scale;
|
|
bool ready_to_cut;
|
|
bool ready_to_move;
|
|
CGAL::qglviewer::Vec v;
|
|
int id;
|
|
|
|
Scene_interface* scene;
|
|
CGAL::qglviewer::ManipulatedFrame* frame;
|
|
};
|
|
|
|
const unsigned int Plane_slider::scale = 100;
|
|
|
|
enum class Directory_extension_type
|
|
{
|
|
DCM = 0,
|
|
BMP
|
|
};
|
|
|
|
class Io_image_plugin :
|
|
public QObject,
|
|
public CGAL::Three::Polyhedron_demo_plugin_helper,
|
|
public CGAL::Three::Polyhedron_demo_io_plugin_interface
|
|
{
|
|
Q_OBJECT
|
|
Q_INTERFACES(CGAL::Three::Polyhedron_demo_io_plugin_interface)
|
|
Q_INTERFACES(CGAL::Three::Polyhedron_demo_plugin_interface)
|
|
Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.IOPluginInterface/1.90" FILE "io_image_plugin.json")
|
|
|
|
public:
|
|
bool applicable(QAction*) const override
|
|
{
|
|
return qobject_cast<Scene_image_item*>(scene->item(scene->mainSelectionIndex()));
|
|
}
|
|
|
|
using Polyhedron_demo_io_plugin_interface::init;
|
|
void init(QMainWindow* mainWindow,
|
|
CGAL::Three::Scene_interface* scene_interface,
|
|
Messages_interface *mi) override
|
|
{
|
|
this->message_interface = mi;
|
|
this->scene = scene_interface;
|
|
this->mw = mainWindow;
|
|
this->is_gray = false;
|
|
|
|
x_control = nullptr;
|
|
y_control = nullptr;
|
|
z_control = nullptr;
|
|
current_control = nullptr;
|
|
|
|
planeSwitch = new QAction("Add Volume Planes", mw);
|
|
|
|
QAction *actionLoadDCM = new QAction("Open Directory (DCM files)", mw);
|
|
connect(actionLoadDCM, SIGNAL(triggered()), this, SLOT(on_actionLoadDCM_triggered()));
|
|
|
|
QAction *actionLoadBMP = new QAction("Open Directory (BMP files)", mw);
|
|
connect(actionLoadBMP, SIGNAL(triggered()), this, SLOT(on_actionLoadBMP_triggered()));
|
|
|
|
if(planeSwitch)
|
|
{
|
|
planeSwitch->setProperty("subMenuName", "3D Mesh Generation");
|
|
connect(planeSwitch, SIGNAL(triggered()),
|
|
this, SLOT(selectPlanes()));
|
|
connect(CGAL::Three::Three::connectableScene(),SIGNAL(itemIndexSelected(int)),
|
|
this, SLOT(connect_controls(int)));
|
|
}
|
|
|
|
Viewer_interface* v = CGAL::Three::Three::mainViewer();
|
|
CGAL_assertion(v != nullptr);
|
|
|
|
pxr_.setViewer(v);
|
|
connect(v, SIGNAL(pointSelected(const QMouseEvent *)), &pxr_, SLOT(update(const QMouseEvent *)));
|
|
createOrGetDockLayout();
|
|
connect(mw, SIGNAL(newViewerCreated(QObject*)),
|
|
this, SLOT(connectNewViewer(QObject*)));
|
|
|
|
QMenu* menuFile = mw->findChild<QMenu*>("menuFile");
|
|
if(nullptr != menuFile )
|
|
{
|
|
QList<QAction*> menuFileActions = menuFile->actions();
|
|
|
|
// Look for action just after "Load..." action
|
|
QAction* actionAfterLoad = nullptr;
|
|
for(QList<QAction*>::iterator it_action = menuFileActions.begin(),
|
|
end = menuFileActions.end() ; it_action != end ; ++ it_action ) //Q_FOREACH( QAction* action, menuFileActions)
|
|
{
|
|
if(NULL != *it_action && (*it_action)->text().contains("Load..."))
|
|
{
|
|
++it_action;
|
|
if(it_action != end && NULL != *it_action)
|
|
{
|
|
actionAfterLoad = *it_action;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Insert "Load implicit function" action
|
|
if(nullptr != actionAfterLoad)
|
|
{
|
|
menuFile->insertAction(actionAfterLoad, actionLoadDCM);
|
|
menuFile->insertAction(actionAfterLoad, actionLoadBMP);
|
|
}
|
|
}
|
|
}
|
|
|
|
QList<QAction*> actions() const override
|
|
{
|
|
return QList<QAction*>() << planeSwitch;
|
|
}
|
|
|
|
virtual void closure() override
|
|
{
|
|
QDockWidget* controlDockWidget = mw->findChild<QDockWidget*>("volumePlanesControl");
|
|
if(controlDockWidget)
|
|
controlDockWidget->hide();
|
|
}
|
|
|
|
Io_image_plugin() : planeSwitch(nullptr) { }
|
|
|
|
QString nameFilters() const override;
|
|
bool canLoad(QFileInfo) const override;
|
|
QList<Scene_item*> load(QFileInfo fileinfo, bool& ok, bool add_to_scene = true) override;
|
|
|
|
bool canSave(const CGAL::Three::Scene_item*) override;
|
|
bool save(QFileInfo fileinfo, QList<CGAL::Three::Scene_item*>& items ) override
|
|
{
|
|
Scene_item* item = items.front();
|
|
const Scene_image_item* im_item = qobject_cast<const Scene_image_item*>(item);
|
|
|
|
point_image p_im = *im_item->image()->image();
|
|
bool ok = _writeImage(&p_im, fileinfo.filePath().toUtf8()) == 0;
|
|
if(ok)
|
|
items.pop_front();
|
|
return ok;
|
|
}
|
|
|
|
bool isDefaultLoader(const Scene_item* item) const override
|
|
{
|
|
if(qobject_cast<const Scene_image_item*>(item))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
QString name() const override{ return "segmented images"; }
|
|
|
|
public Q_SLOTS:
|
|
void setXNum(int i)
|
|
{
|
|
x_cubeLabel->setText(QString("%1").arg(i));
|
|
}
|
|
void setYNum(int i)
|
|
{
|
|
y_cubeLabel->setText(QString("%1").arg(i));
|
|
}
|
|
void setZNum(int i)
|
|
{
|
|
z_cubeLabel->setText(QString("%1").arg(i));
|
|
}
|
|
|
|
void XTextEdited(QString s)
|
|
{
|
|
int i = s.toInt();
|
|
x_slider->setValue(i*qobject_cast<Plane_slider*>(x_slider)->getScale());
|
|
x_slider->sliderMoved(i);
|
|
}
|
|
void YTextEdited(QString s)
|
|
{
|
|
int i = s.toInt();
|
|
y_slider->setValue(i*qobject_cast<Plane_slider*>(y_slider)->getScale());
|
|
y_slider->sliderMoved(i);
|
|
}
|
|
void ZTextEdited(QString s)
|
|
{
|
|
int i = s.toInt();
|
|
z_slider->setValue(i*qobject_cast<Plane_slider*>(z_slider)->getScale());
|
|
z_slider->sliderMoved(i);
|
|
}
|
|
|
|
void on_imageType_changed(int index)
|
|
{
|
|
if(index == 0)
|
|
ui.groupBox->setVisible(true);
|
|
else
|
|
ui.groupBox->setVisible(false);
|
|
}
|
|
|
|
void selectPlanes()
|
|
{
|
|
std::vector< Scene_image_item* > seg_items;
|
|
Scene_image_item* seg_img;
|
|
seg_img = nullptr;
|
|
for(int i = 0; i < scene->numberOfEntries(); ++i)
|
|
{
|
|
Scene_image_item* tmp = qobject_cast<Scene_image_item*>(scene->item(i));
|
|
if(tmp != nullptr)
|
|
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_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_image_item*>::const_iterator it = seg_items.begin();
|
|
it != seg_items.end(); ++it)
|
|
{
|
|
if(selected == (*it)->name())
|
|
seg_img = *it;
|
|
}
|
|
}
|
|
|
|
if(group_map.keys().contains(seg_img))
|
|
{
|
|
CGAL::Three::Three::warning("This item already has volume planes.");
|
|
}
|
|
else
|
|
{
|
|
// Opens a modal Dialog to prevent the user from manipulating things that could mess with the planes creation and cause a segfault.
|
|
msgBox.setText("Planes created : 0/3");
|
|
msgBox.setStandardButtons(QMessageBox::NoButton);
|
|
msgBox.show();
|
|
createPlanes(seg_img);
|
|
}
|
|
}
|
|
|
|
void addVP(Volume_plane_thread* thread)
|
|
{
|
|
Volume_plane_interface* plane = thread->getItem();
|
|
plane->init(Three::mainViewer());
|
|
|
|
// add the interface for this Volume_plane
|
|
int id = scene->addItem(plane);
|
|
scene->changeGroup(plane, group);
|
|
group->lockChild(plane);
|
|
//connect(plane->manipulatedFrame(), &CGAL::qglviewer::ManipulatedFrame::manipulated,
|
|
// plane, &Volume_plane_interface::redraw);
|
|
|
|
switch(thread->type())
|
|
{
|
|
case 'x':
|
|
delete x_slider;
|
|
x_slider = new Plane_slider(plane->translationVector(), id, scene, plane->manipulatedFrame(),
|
|
Qt::Horizontal, x_control);
|
|
x_slider->setRange(0, (plane->cDim() - 1) * 100);
|
|
connect(x_slider, SIGNAL(realChange(int)), this, SLOT(setXNum(int)));
|
|
connect(x_cubeLabel, SIGNAL(textEdited(QString)), this, SLOT(XTextEdited(QString)));
|
|
connect(x_slider, SIGNAL(realChange(int)), this, SLOT(set_value()));
|
|
connect(x_slider, SIGNAL(sliderMoved(int)), x_slider, SLOT(updateFramePosition()));
|
|
connect(plane, SIGNAL(manipulated(int)), this, SLOT(setXNum(int)));
|
|
connect(plane, SIGNAL(aboutToBeDestroyed()), this, SLOT(destroy_x_item()));
|
|
x_box->addWidget(x_slider);
|
|
x_box->addWidget(x_cubeLabel);
|
|
x_control->setVisible(true);
|
|
break;
|
|
case 'y':
|
|
delete y_slider;
|
|
y_slider = new Plane_slider(plane->translationVector(), id, scene, plane->manipulatedFrame(),
|
|
Qt::Horizontal, y_control);
|
|
y_slider->setRange(0, (plane->cDim() - 1) * 100);
|
|
connect(y_slider, SIGNAL(realChange(int)), this, SLOT(setYNum(int)));
|
|
connect(y_cubeLabel, SIGNAL(textEdited(QString)), this, SLOT(YTextEdited(QString)));
|
|
connect(y_slider, SIGNAL(realChange(int)), this, SLOT(set_value()));
|
|
connect(y_slider, SIGNAL(sliderMoved(int)), y_slider, SLOT(updateFramePosition()));
|
|
connect(plane, SIGNAL(manipulated(int)), this, SLOT(setYNum(int)));
|
|
connect(plane, SIGNAL(aboutToBeDestroyed()), this, SLOT(destroy_y_item()));
|
|
y_box->addWidget(y_slider);
|
|
y_box->addWidget(y_cubeLabel);
|
|
y_control->setVisible(true);
|
|
break;
|
|
case 'z':
|
|
delete z_slider;
|
|
z_slider = new Plane_slider(plane->translationVector(), id, scene, plane->manipulatedFrame(),
|
|
Qt::Horizontal, z_control);
|
|
z_slider->setRange(0, (plane->cDim() - 1) * 100);
|
|
connect(z_slider, SIGNAL(realChange(int)), this, SLOT(setZNum(int)));
|
|
connect(z_cubeLabel, SIGNAL(textEdited(QString)), this, SLOT(ZTextEdited(QString)));
|
|
connect(z_slider, SIGNAL(realChange(int)), this, SLOT(set_value()));
|
|
connect(z_slider, SIGNAL(sliderMoved(int)), z_slider, SLOT(updateFramePosition()));
|
|
connect(plane, SIGNAL(manipulated(int)), this, SLOT(setZNum(int)));
|
|
connect(plane, SIGNAL(aboutToBeDestroyed()), this, SLOT(destroy_z_item()));
|
|
z_box->addWidget(z_slider);
|
|
z_box->addWidget(z_cubeLabel);
|
|
z_control->setVisible(true);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
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);
|
|
|
|
update_msgBox();
|
|
Volume_plane_intersection* intersection = dynamic_cast<Volume_plane_intersection*>(scene->item(intersection_id));
|
|
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*)));
|
|
|
|
}
|
|
|
|
void loadDirectory(const Directory_extension_type ext)
|
|
{
|
|
QSettings settings;
|
|
QString start_dir = settings.value("Open directory", QDir::current().dirName()).toString();
|
|
QString dir = QFileDialog::getExistingDirectory(mw, tr("Open directory"),
|
|
start_dir, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
|
|
|
|
if(!dir.isEmpty())
|
|
{
|
|
QFileInfo fileinfo(dir);
|
|
if(fileinfo.isDir() && fileinfo.isReadable())
|
|
{
|
|
settings.setValue("Open directory", fileinfo.absoluteDir().absolutePath());
|
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
|
QApplication::processEvents();
|
|
|
|
loadDirectory(dir, ext);
|
|
}
|
|
}
|
|
}
|
|
|
|
void on_actionLoadDCM_triggered()
|
|
{
|
|
return loadDirectory(Directory_extension_type::DCM);
|
|
}
|
|
|
|
void on_actionLoadBMP_triggered()
|
|
{
|
|
return loadDirectory(Directory_extension_type::BMP);
|
|
}
|
|
|
|
void connectNewViewer(QObject* o)
|
|
{
|
|
Q_FOREACH(Controls c, group_map.values())
|
|
{
|
|
o->installEventFilter(c.x_item);
|
|
o->installEventFilter(c.y_item);
|
|
o->installEventFilter(c.z_item);
|
|
}
|
|
}
|
|
|
|
private:
|
|
CGAL::qglviewer::Vec first_offset;
|
|
bool is_gray;
|
|
|
|
Messages_interface* message_interface;
|
|
QMessageBox msgBox;
|
|
|
|
QAction* planeSwitch;
|
|
QWidget *x_control, *y_control, *z_control;
|
|
QSlider *x_slider, *y_slider, *z_slider;
|
|
QLineEdit*x_cubeLabel, *y_cubeLabel, *z_cubeLabel;
|
|
QHBoxLayout *x_box, *y_box, *z_box;
|
|
PixelReader pxr_;
|
|
Ui::ImagePrecisionDialog ui;
|
|
|
|
CGAL::Three::Scene_group_item* group;
|
|
std::vector<Volume_plane_thread*> threads;
|
|
|
|
struct Controls
|
|
{
|
|
CGAL::Three::Scene_item* group;
|
|
CGAL::Three::Scene_item* x_item;
|
|
CGAL::Three::Scene_item* y_item;
|
|
CGAL::Three::Scene_item* z_item;
|
|
int x_value;
|
|
int y_value;
|
|
int z_value;
|
|
};
|
|
|
|
Controls* current_control;
|
|
QMap<CGAL::Three::Scene_item*, Controls> group_map;
|
|
unsigned int intersection_id;
|
|
|
|
bool loadDirectory(const QString& filename, const Directory_extension_type ext);
|
|
Image* createDirectoryImage(const QString& dirname, const Directory_extension_type ext, const bool smooth);
|
|
|
|
QLayout* createOrGetDockLayout()
|
|
{
|
|
QLayout* layout = nullptr;
|
|
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("Volume Planes Control");
|
|
mw->addDockWidget(Qt::LeftDockWidgetArea, controlDockWidget);
|
|
|
|
QWidget* vlabels = new QWidget(content);
|
|
layout->addWidget(vlabels);
|
|
layout->setAlignment(Qt::AlignLeft);
|
|
QHBoxLayout* vbox = new QHBoxLayout(vlabels);
|
|
vbox->setAlignment(Qt::AlignLeft);
|
|
QLabel* text = new QLabel(vlabels);
|
|
text->setText("Value of that pixel:");
|
|
QLabel* help = new QLabel(vlabels);
|
|
help->setText("Cut planes for the selected image:");
|
|
QLabel* x = new QLabel(vlabels);
|
|
|
|
connect(&pxr_, SIGNAL(x(QString)), x, SLOT(setText(QString)));
|
|
|
|
layout->addWidget(help); vbox->addWidget(text); vbox->addWidget(x);
|
|
controlDockWidget->setWidget(content);
|
|
controlDockWidget->hide();
|
|
|
|
}
|
|
else
|
|
{
|
|
layout = controlDockWidget->findChild<QLayout*>("vpSliderLayout");
|
|
controlDockWidget->show();
|
|
controlDockWidget->raise();
|
|
}
|
|
|
|
return layout;
|
|
}
|
|
|
|
void createPlanes(Scene_image_item* seg_img)
|
|
{
|
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
|
//Control widgets creation
|
|
QLayout* layout = createOrGetDockLayout();
|
|
QRegularExpressionValidator* validator = new QRegularExpressionValidator(QRegularExpression("\\d*"), this);
|
|
bool show_sliders = true;
|
|
if(x_control == nullptr)
|
|
{
|
|
x_control = new QWidget;
|
|
x_box = new QHBoxLayout(x_control);
|
|
layout->addWidget(x_control);
|
|
|
|
QLabel* label = new QLabel(x_control);
|
|
label->setStyleSheet("QLabel { color : red; }");
|
|
label->setText("X Slice");
|
|
|
|
x_cubeLabel = new QLineEdit(x_control);
|
|
|
|
// Find the right width for the label to accommodate at least 9999
|
|
QFontMetrics metric = x_cubeLabel->fontMetrics();
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
|
|
x_cubeLabel->setFixedWidth(metric.horizontalAdvance(QString(".9999.")));
|
|
#else
|
|
x_cubeLabel->setFixedWidth(metric.width(QString(".9999.")));
|
|
#endif
|
|
x_cubeLabel->setText("0");
|
|
x_cubeLabel->setValidator(validator);
|
|
|
|
x_slider = new QSlider(mw);
|
|
|
|
x_box->addWidget(label);
|
|
x_box->addWidget(x_slider);
|
|
x_box->addWidget(x_cubeLabel);
|
|
show_sliders &= seg_img->image()->xdim() > 1;
|
|
}
|
|
|
|
if(y_control == nullptr)
|
|
{
|
|
y_control = new QWidget;
|
|
y_box = new QHBoxLayout(y_control);
|
|
layout->addWidget(y_control);
|
|
|
|
QLabel* label = new QLabel(y_control);
|
|
label->setStyleSheet("QLabel { color : green; }");
|
|
label->setText("Y Slice");
|
|
|
|
y_cubeLabel = new QLineEdit(y_control);
|
|
|
|
// Find the right width for the label to accommodate at least 9999
|
|
QFontMetrics metric = y_cubeLabel->fontMetrics();
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
|
|
y_cubeLabel->setFixedWidth(metric.horizontalAdvance(QString(".9999.")));
|
|
#else
|
|
y_cubeLabel->setFixedWidth(metric.width(QString(".9999.")));
|
|
#endif
|
|
y_cubeLabel->setText("0");
|
|
y_cubeLabel->setValidator(validator);
|
|
y_slider = new QSlider(mw);
|
|
|
|
y_box->addWidget(label);
|
|
y_box->addWidget(y_slider);
|
|
y_box->addWidget(y_cubeLabel);
|
|
show_sliders &= seg_img->image()->ydim() > 1;
|
|
}
|
|
|
|
if(z_control == nullptr)
|
|
{
|
|
z_control = new QWidget;
|
|
z_box = new QHBoxLayout(z_control);
|
|
layout->addWidget(z_control);
|
|
|
|
QLabel* label = new QLabel(z_control);
|
|
label->setStyleSheet("QLabel { color : blue; }");
|
|
label->setText("Z Slice");
|
|
|
|
z_cubeLabel = new QLineEdit(z_control);
|
|
|
|
// Find the right width for the label to accommodate at least 9999
|
|
QFontMetrics metric = z_cubeLabel->fontMetrics();
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
|
|
z_cubeLabel->setFixedWidth(metric.horizontalAdvance(QString(".9999.")));
|
|
#else
|
|
z_cubeLabel->setFixedWidth(metric.width(QString(".9999.")));
|
|
#endif
|
|
z_cubeLabel->setText("0");
|
|
z_cubeLabel->setValidator(validator);
|
|
z_slider = new QSlider(mw);
|
|
|
|
z_box->addWidget(label);
|
|
z_box->addWidget(z_slider);
|
|
z_box->addWidget(z_cubeLabel);
|
|
show_sliders &= seg_img->image()->zdim() > 1;
|
|
}
|
|
|
|
x_control->setEnabled(show_sliders);
|
|
y_control->setEnabled(show_sliders);
|
|
z_control->setEnabled(show_sliders);
|
|
|
|
if(!(seg_img == nullptr))
|
|
{
|
|
const CGAL::Image_3* img = seg_img->image();
|
|
CGAL_IMAGE_IO_CASE(img->image(), this->launchAdders<Word>(seg_img, seg_img->name()))
|
|
|
|
Volume_plane_intersection* i
|
|
= new Volume_plane_intersection(img->xdim() * img->vx()-1,
|
|
img->ydim() * img->vy()-1,
|
|
img->zdim() * img->vz()-1,
|
|
img->image()->tx,
|
|
img->image()->ty,
|
|
img->image()->tz);
|
|
this->intersection_id = scene->addItem(i);
|
|
scene->changeGroup(i, group);
|
|
group->lockChild(i);
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::warning(mw, tr("Something went wrong"), tr("Selected a suitable Object but couldn't get an image pointer."));
|
|
return;
|
|
}
|
|
}
|
|
|
|
template<typename Word>
|
|
void launchAdders(Scene_image_item* seg_img, const QString& name)
|
|
{
|
|
const CGAL::Image_3* img = seg_img->image();
|
|
const Word* begin = (const Word*)img->data();
|
|
const Word* end = (const Word*)img->data() + img->size();
|
|
|
|
std::pair<const Word, const Word> minmax(*std::min_element(begin, end), *std::max_element(begin, end));
|
|
|
|
Clamp_to_one_zero_range clamper = { minmax };
|
|
|
|
switchReaderConverter< Word >(minmax);
|
|
|
|
Volume_plane<x_tag> *x_item = new Volume_plane<x_tag>(img->image()->tx,img->image()->ty, img->image()->tz);
|
|
Volume_plane<y_tag> *y_item = new Volume_plane<y_tag>(img->image()->tx,img->image()->ty, img->image()->tz);
|
|
Volume_plane<z_tag> *z_item = new Volume_plane<z_tag>(img->image()->tx,img->image()->ty, img->image()->tz);
|
|
|
|
x_item->setProperty("img", QVariant::fromValue((void*)seg_img));
|
|
y_item->setProperty("img", QVariant::fromValue((void*)seg_img));
|
|
z_item->setProperty("img", QVariant::fromValue((void*)seg_img));
|
|
|
|
x_item->setColor(QColor("red"));
|
|
y_item->setColor(QColor("green"));
|
|
z_item->setColor(QColor("blue"));
|
|
Q_FOREACH(CGAL::QGLViewer* viewer, CGAL::QGLViewer::QGLViewerPool())
|
|
{
|
|
viewer->installEventFilter(x_item);
|
|
viewer->installEventFilter(y_item);
|
|
viewer->installEventFilter(z_item);
|
|
}
|
|
|
|
connect(x_item, SIGNAL(selected(CGAL::Three::Scene_item*)), this, SLOT(select_plane(CGAL::Three::Scene_item*)));
|
|
connect(y_item, SIGNAL(selected(CGAL::Three::Scene_item*)), this, SLOT(select_plane(CGAL::Three::Scene_item*)));
|
|
connect(z_item, SIGNAL(selected(CGAL::Three::Scene_item*)), this, SLOT(select_plane(CGAL::Three::Scene_item*)));
|
|
scene->setSelectedItem(-1);
|
|
group = new Scene_group_item(QString("Planes for %1").arg(seg_img->name()));
|
|
connect(group, SIGNAL(aboutToBeDestroyed()),
|
|
this, SLOT(erase_group()));
|
|
scene->addItem(group);
|
|
|
|
Controls c;
|
|
c.group = group;
|
|
c.x_item = x_item;
|
|
c.y_item = y_item;
|
|
c.z_item = z_item;
|
|
c.x_value = 0;
|
|
c.y_value = 0;
|
|
c.z_value = 0;
|
|
group_map[seg_img] = c;
|
|
current_control = &group_map[seg_img];
|
|
connect(seg_img, SIGNAL(aboutToBeDestroyed()),
|
|
this, SLOT(on_img_detroyed()));
|
|
|
|
threads.push_back(new X_plane_thread<Word>(x_item, 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>(y_item,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>(z_item,img, clamper, name));
|
|
connect(threads.back(), SIGNAL(finished(Volume_plane_thread*)), this, SLOT(addVP(Volume_plane_thread*)));
|
|
threads.back()->start();
|
|
|
|
first_offset = Three::mainViewer()->offset();
|
|
|
|
}
|
|
|
|
template<typename T>
|
|
void switchReaderConverter(std::pair<T, T> minmax)
|
|
{
|
|
switchReaderConverter(minmax, typename std::is_integral<T>::type());
|
|
}
|
|
|
|
template<typename T>
|
|
void switchReaderConverter(std::pair<T, T> minmax, std::true_type)
|
|
{
|
|
// IntConverter
|
|
IntConverter x = { minmax }; pxr_.setIC(x);
|
|
}
|
|
|
|
template<typename T>
|
|
void switchReaderConverter(std::pair<T, T> minmax, std::false_type)
|
|
{
|
|
// IntConverter
|
|
DoubleConverter x = { minmax }; pxr_.setFC(x);
|
|
}
|
|
|
|
private Q_SLOTS:
|
|
void select_plane(CGAL::Three::Scene_item* item)
|
|
{
|
|
Scene_image_item* img = (Scene_image_item*)item->property("img").value<void*>();
|
|
if(img)
|
|
scene->setSelectedItem(scene->item_id(img));
|
|
}
|
|
//updates the msgBox
|
|
void update_msgBox()
|
|
{
|
|
static int nbPlanes =0;
|
|
++nbPlanes;
|
|
msgBox.setText(QString("Planes created : %1/3").arg(nbPlanes));
|
|
if(nbPlanes == 3)
|
|
{
|
|
const CGAL::qglviewer::Vec offset = Three::mainViewer()->offset();
|
|
if(offset != first_offset)
|
|
{
|
|
for(int i=0; i<scene->numberOfEntries(); ++i)
|
|
{
|
|
scene->item(i)->invalidateOpenGLBuffers();
|
|
}
|
|
}
|
|
|
|
msgBox.hide();
|
|
nbPlanes = 0;
|
|
QApplication::restoreOverrideCursor();
|
|
}
|
|
}
|
|
// Avoids the segfault after the deletion of an item
|
|
void erase_group()
|
|
{
|
|
CGAL::Three::Scene_group_item* group_item = qobject_cast<CGAL::Three::Scene_group_item*>(sender());
|
|
if(group_item)
|
|
{
|
|
Q_FOREACH(CGAL::Three::Scene_item* key, group_map.keys())
|
|
{
|
|
if(group_map[key].group == group_item)
|
|
{
|
|
group_map[key].x_item = nullptr;
|
|
group_map[key].y_item = nullptr;
|
|
group_map[key].z_item = nullptr;
|
|
group_map.remove(key);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//try to re-connect to another group
|
|
if(!group_map.isEmpty())
|
|
{
|
|
int id = scene->item_id(group_map.keys().first());
|
|
connect_controls(id);
|
|
}
|
|
}
|
|
|
|
// destroy planes on image deletion
|
|
void on_img_detroyed()
|
|
{
|
|
Scene_image_item* img_item = qobject_cast<Scene_image_item*>(sender());
|
|
if(img_item)
|
|
{
|
|
Scene_group_item* group = qobject_cast<Scene_group_item*>(group_map[img_item].group);
|
|
if(!group)
|
|
return;
|
|
|
|
group_map[img_item].x_item = nullptr;
|
|
group_map[img_item].y_item = nullptr;
|
|
group_map[img_item].z_item = nullptr;
|
|
disconnect(group_map[img_item].group, SIGNAL(aboutToBeDestroyed()),
|
|
this, SLOT(erase_group()));
|
|
group_map.remove(img_item);
|
|
|
|
QList<int> deletion;
|
|
Q_FOREACH(Scene_interface::Item_id id, group->getChildren())
|
|
{
|
|
Scene_item* child = group->getChild(id);
|
|
group->unlockChild(child);
|
|
deletion.append(scene->item_id(child));
|
|
}
|
|
deletion.append(scene->item_id(group));
|
|
scene->erase(deletion);
|
|
}
|
|
|
|
//try to re-connect to another group
|
|
if(!group_map.isEmpty())
|
|
{
|
|
int id = scene->item_id(group_map.keys().first());
|
|
connect_controls(id);
|
|
}
|
|
}
|
|
|
|
void connect_controls(int id)
|
|
{
|
|
CGAL::Three::Scene_item* sel_itm = scene->item(id);
|
|
if(!sel_itm)
|
|
return;
|
|
|
|
if(!group_map.contains(sel_itm)) //the planes are not yet created or the selected item is not a segmented_image
|
|
{
|
|
Scene_image_item* img = (Scene_image_item*)sel_itm->property("img").value<void*>();
|
|
if(img)
|
|
sel_itm = img;
|
|
else
|
|
return;
|
|
}
|
|
|
|
Controls c = group_map[sel_itm];
|
|
current_control = &group_map[sel_itm];
|
|
bool show_sliders = true;
|
|
|
|
// x line
|
|
if(c.x_item != nullptr)
|
|
{
|
|
Volume_plane_interface* x_plane = qobject_cast<Volume_plane_interface*>(c.x_item);
|
|
if(x_slider)
|
|
delete x_slider;
|
|
|
|
x_slider = new Plane_slider(x_plane->translationVector(), scene->item_id(x_plane), scene, x_plane->manipulatedFrame(),
|
|
Qt::Horizontal, x_control);
|
|
x_slider->setRange(0, (x_plane->cDim() - 1) * 100);
|
|
connect(x_slider, SIGNAL(realChange(int)), this, SLOT(setXNum(int)));
|
|
connect(x_plane, SIGNAL(manipulated(int)), this, SLOT(setXNum(int)));
|
|
connect(x_cubeLabel, SIGNAL(textEdited(QString)), this, SLOT(XTextEdited(QString)));
|
|
connect(x_plane, SIGNAL(aboutToBeDestroyed()), this, SLOT(destroy_x_item()));
|
|
connect(x_slider, SIGNAL(realChange(int)), this, SLOT(set_value()));
|
|
connect(x_slider, SIGNAL(sliderMoved(int)), x_slider, SLOT(updateFramePosition()));
|
|
x_slider->setValue(c.x_value);
|
|
|
|
x_box->addWidget(x_slider);
|
|
x_box->addWidget(x_cubeLabel);
|
|
show_sliders &= qobject_cast<Scene_image_item*>(sel_itm)->image()->xdim() > 1;
|
|
}
|
|
|
|
//y line
|
|
if(c.y_item != nullptr)
|
|
{
|
|
Volume_plane_interface* y_plane = qobject_cast<Volume_plane_interface*>(c.y_item);
|
|
if(y_slider)
|
|
delete y_slider;
|
|
|
|
y_slider = new Plane_slider(y_plane->translationVector(), scene->item_id(y_plane), scene, y_plane->manipulatedFrame(),
|
|
Qt::Horizontal, z_control);
|
|
y_slider->setRange(0, (y_plane->cDim() - 1) * 100);
|
|
connect(y_slider, SIGNAL(realChange(int)), this, SLOT(setYNum(int)));
|
|
connect(y_plane, SIGNAL(manipulated(int)), this, SLOT(setYNum(int)));
|
|
connect(y_cubeLabel, SIGNAL(textEdited(QString)), this, SLOT(YTextEdited(QString)));
|
|
connect(y_plane, SIGNAL(aboutToBeDestroyed()), this, SLOT(destroy_y_item()));
|
|
connect(y_slider, SIGNAL(realChange(int)), this, SLOT(set_value()));
|
|
connect(y_slider, SIGNAL(sliderMoved(int)), y_slider, SLOT(updateFramePosition()));
|
|
y_slider->setValue(c.y_value);
|
|
y_box->addWidget(y_slider);
|
|
y_box->addWidget(y_cubeLabel);
|
|
show_sliders &= qobject_cast<Scene_image_item*>(sel_itm)->image()->ydim() > 1;
|
|
}
|
|
|
|
// z line
|
|
if(c.z_item != nullptr)
|
|
{
|
|
Volume_plane_interface* z_plane = qobject_cast<Volume_plane_interface*>(c.z_item);
|
|
if(z_slider)
|
|
delete z_slider;
|
|
|
|
z_slider = new Plane_slider(z_plane->translationVector(), scene->item_id(z_plane), scene, z_plane->manipulatedFrame(),
|
|
Qt::Horizontal, z_control);
|
|
z_slider->setRange(0, (z_plane->cDim() - 1) * 100);
|
|
connect(z_slider, SIGNAL(realChange(int)), this, SLOT(setZNum(int)));
|
|
connect(z_plane, SIGNAL(manipulated(int)), this, SLOT(setZNum(int)));
|
|
connect(z_cubeLabel, SIGNAL(textEdited(QString)), this, SLOT(ZTextEdited(QString)));
|
|
connect(z_plane, SIGNAL(aboutToBeDestroyed()), this, SLOT(destroy_z_item()));
|
|
connect(z_slider, SIGNAL(sliderMoved(int)), z_slider, SLOT(updateFramePosition()));
|
|
connect(z_slider, SIGNAL(realChange(int)), this, SLOT(set_value()));
|
|
z_slider->setValue(c.z_value);
|
|
z_box->addWidget(z_slider);
|
|
z_box->addWidget(z_cubeLabel);
|
|
show_sliders &= qobject_cast<Scene_image_item*>(sel_itm)->image()->zdim() > 1;
|
|
}
|
|
|
|
x_control->setEnabled(show_sliders);
|
|
y_control->setEnabled(show_sliders);
|
|
z_control->setEnabled(show_sliders);
|
|
}
|
|
|
|
// Keeps the position of the planes for the next time
|
|
void set_value()
|
|
{
|
|
current_control->x_value = x_slider->value();
|
|
current_control->y_value = y_slider->value();
|
|
current_control->z_value = z_slider->value();
|
|
}
|
|
|
|
void destroy_x_item()
|
|
{
|
|
current_control->x_item = nullptr;
|
|
if(group_map.isEmpty())
|
|
x_control->hide();
|
|
}
|
|
void destroy_y_item()
|
|
{
|
|
current_control->y_item = nullptr;
|
|
if(group_map.isEmpty())
|
|
y_control->hide();
|
|
}
|
|
void destroy_z_item()
|
|
{
|
|
current_control->z_item = nullptr;
|
|
if(group_map.isEmpty())
|
|
z_control->hide();
|
|
}
|
|
};
|
|
|
|
QString Io_image_plugin::nameFilters() const
|
|
{
|
|
return QString("Inrimage files (*.inr *.inr.gz) ;; "
|
|
"Analyze files (*.hdr *.img *.img.gz) ;; "
|
|
"Stanford Exploration Project files (*.H *.HH) ;; "
|
|
"VTK image files (*.vti) ;; "
|
|
"NRRD image files (*.nrrd) ;; "
|
|
"NIFTI image files (*.nii *.nii.gz)");
|
|
}
|
|
|
|
bool Io_image_plugin::canLoad(QFileInfo) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
template<typename Word>
|
|
void convert(Image* image)
|
|
{
|
|
float *f_data = (float*)ImageIO_alloc(image->xdim()*image->ydim()*image->zdim()*sizeof(float));
|
|
Word* d_data = (Word*)(image->data());
|
|
|
|
// convert image from double to float
|
|
for(std::size_t x = 0; x<image->xdim(); ++x) {
|
|
for(std::size_t y = 0; y<image->ydim(); ++y) {
|
|
for(std::size_t z = 0; z<image->zdim(); ++z)
|
|
{
|
|
std::size_t i =(z * image->ydim() + y) * image->xdim() + x;
|
|
f_data[i] =(float)d_data[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
ImageIO_free(d_data);
|
|
image->image()->data = (void*)f_data;
|
|
image->image()->wdim = 4;
|
|
image->image()->wordKind = WK_FLOAT;
|
|
}
|
|
|
|
|
|
QList<Scene_item*>
|
|
Io_image_plugin::load(QFileInfo fileinfo, bool& ok, bool add_to_scene)
|
|
{
|
|
ok = true;
|
|
QApplication::restoreOverrideCursor();
|
|
Image* image = new Image;
|
|
|
|
QString warningMessage;
|
|
// read a vti file
|
|
if(fileinfo.suffix() == "vti")
|
|
{
|
|
#ifdef CGAL_USE_VTK
|
|
ok = load_vtk_file<vtkXMLImageDataReader>(fileinfo, image);
|
|
#else
|
|
ok = false;
|
|
warningMessage = "VTK is required to read VTI files";
|
|
#endif
|
|
}
|
|
|
|
// read a nrrd file
|
|
else if(fileinfo.suffix() == "nrrd")
|
|
{
|
|
#ifdef CGAL_USE_VTK
|
|
ok = load_vtk_file<vtkNrrdReader>(fileinfo, image);
|
|
#else
|
|
ok = false;
|
|
warningMessage = "VTK is required to read NRRD files";
|
|
#endif
|
|
}
|
|
|
|
// read a NIFTI file
|
|
else if(fileinfo.suffix() == "nii"
|
|
|| ( fileinfo.suffix() == "gz"
|
|
&& fileinfo.fileName().endsWith(QString(".nii.gz"), Qt::CaseInsensitive)))
|
|
{
|
|
#ifdef CGAL_USE_VTK
|
|
ok = load_vtk_file<vtkNIFTIImageReader>(fileinfo, image);
|
|
#else
|
|
ok = false;
|
|
warningMessage = "VTK is required to read NifTI files";
|
|
#endif
|
|
}
|
|
|
|
// read a sep file
|
|
else if (fileinfo.suffix() == "H" || fileinfo.suffix() == "HH")
|
|
{
|
|
CGAL::SEP_to_ImageIO<float> reader(fileinfo.filePath().toUtf8().data());
|
|
*image = *reader.cgal_image();
|
|
is_gray = true;
|
|
}
|
|
|
|
else if(fileinfo.suffix() != "H" && fileinfo.suffix() != "HH" &&
|
|
!image->read(fileinfo.filePath().toUtf8()))
|
|
{
|
|
QMessageBox qmb(QMessageBox::NoIcon,
|
|
"Raw Dialog",
|
|
tr("Error with file <tt>%1</tt>:\n"
|
|
"unknown file format!\n"
|
|
"\n"
|
|
"Open it as a raw image?").arg(fileinfo.fileName()),
|
|
QMessageBox::Yes|QMessageBox::No);
|
|
|
|
bool success = true;
|
|
if(qmb.exec() == QMessageBox::Yes)
|
|
{
|
|
Raw_image_dialog raw_dialog;
|
|
raw_dialog.label_file_size->setText(QString("%1 B").arg(fileinfo.size()));
|
|
raw_dialog.buttonBox->button(QDialogButtonBox::Open)->setEnabled(false);
|
|
if(raw_dialog.exec())
|
|
{
|
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
|
QApplication::processEvents();
|
|
|
|
if(image->read_raw(fileinfo.filePath().toUtf8(),
|
|
raw_dialog.dim_x->value(),
|
|
raw_dialog.dim_y->value(),
|
|
raw_dialog.dim_z->value(),
|
|
raw_dialog.spacing_x->value(),
|
|
raw_dialog.spacing_y->value(),
|
|
raw_dialog.spacing_z->value(),
|
|
raw_dialog.offset->value(),
|
|
raw_dialog.image_word_size(),
|
|
raw_dialog.image_word_kind(),
|
|
raw_dialog.image_sign()))
|
|
{
|
|
switch(raw_dialog.image_word_kind())
|
|
{
|
|
case WK_FLOAT:
|
|
is_gray = true;
|
|
convert<double>(image);
|
|
break;
|
|
case WK_FIXED:
|
|
{
|
|
switch(raw_dialog.image_word_size())
|
|
{
|
|
case 2:
|
|
is_gray = true;
|
|
convert<short>(image);
|
|
break;
|
|
case 4:
|
|
is_gray = true;
|
|
convert<int>(image);
|
|
break;
|
|
default:
|
|
is_gray = false;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
QSettings settings;
|
|
settings.beginGroup(QUrl::toPercentEncoding(fileinfo.absoluteFilePath()));
|
|
settings.setValue("is_raw", true);
|
|
settings.setValue("dim_x", raw_dialog.dim_x->value());
|
|
settings.setValue("dim_y", raw_dialog.dim_y->value());
|
|
settings.setValue("dim_z", raw_dialog.dim_z->value());
|
|
settings.setValue("spacing_x", raw_dialog.spacing_x->value());
|
|
settings.setValue("spacing_y", raw_dialog.spacing_y->value());
|
|
settings.setValue("spacing_z", raw_dialog.spacing_z->value());
|
|
settings.setValue("offset", raw_dialog.offset->value());
|
|
settings.setValue("wdim", QVariant::fromValue(raw_dialog.image_word_size()));
|
|
settings.setValue("wk", raw_dialog.image_word_kind());
|
|
settings.setValue("sign", raw_dialog.image_sign());
|
|
settings.endGroup();
|
|
}
|
|
else
|
|
{
|
|
success = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
success = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
success = false;
|
|
}
|
|
|
|
if(!success)
|
|
{
|
|
ok = false;
|
|
}
|
|
}
|
|
|
|
if (!ok) {
|
|
if (warningMessage.length() > 0)
|
|
CGAL::Three::Three::warning(warningMessage);
|
|
delete image;
|
|
return QList<Scene_item*>();
|
|
}
|
|
|
|
// Get display precision
|
|
QDialog dialog;
|
|
ui.setupUi(&dialog);
|
|
|
|
connect(ui.buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
|
|
connect(ui.buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
|
|
connect(ui.imageType, SIGNAL(currentIndexChanged(int)),
|
|
this, SLOT(on_imageType_changed(int)));
|
|
dialog.setWindowFlags(Qt::Dialog|Qt::CustomizeWindowHint|Qt::WindowCloseButtonHint);
|
|
|
|
// Add precision values to the dialog
|
|
for(int i=1 ; i<9; ++i)
|
|
{
|
|
QString s = tr("1:%1").arg(i*i*i);
|
|
ui.precisionList->addItem(s);
|
|
}
|
|
|
|
// Adds Image type
|
|
ui.imageType->addItem(QString("Segmented image"));
|
|
ui.imageType->addItem(QString("Gray-level image"));
|
|
|
|
QString type;
|
|
int voxel_scale = 0;
|
|
|
|
// Open window
|
|
QApplication::restoreOverrideCursor();
|
|
if(!is_gray)
|
|
{
|
|
int return_code = dialog.exec();
|
|
if(return_code != QDialog::Accepted)
|
|
{
|
|
delete image;
|
|
ok = false;
|
|
return QList<Scene_item*>();
|
|
}
|
|
|
|
// Get selected precision
|
|
voxel_scale = ui.precisionList->currentIndex() + 1;
|
|
|
|
//Get the image type
|
|
type = ui.imageType->currentText();
|
|
}
|
|
else
|
|
{
|
|
type = "Gray-level image";
|
|
}
|
|
|
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
|
QApplication::processEvents();
|
|
|
|
Scene_image_item* image_item;
|
|
if(type == "Gray-level image")
|
|
{
|
|
//Create planes
|
|
image_item = new Scene_image_item(image,0, true);
|
|
image_item->setName(fileinfo.baseName());
|
|
msgBox.setText("Planes created : 0/3");
|
|
msgBox.setStandardButtons(QMessageBox::NoButton);
|
|
msgBox.show();
|
|
|
|
createPlanes(image_item);
|
|
}
|
|
else
|
|
{
|
|
image_item = new Scene_image_item(image,voxel_scale, false);
|
|
}
|
|
image_item->setName(fileinfo.baseName());
|
|
|
|
if(add_to_scene)
|
|
CGAL::Three::Three::scene()->addItem(image_item);
|
|
|
|
return QList<Scene_item*>() << image_item;
|
|
}
|
|
|
|
bool Io_image_plugin::canSave(const CGAL::Three::Scene_item* item)
|
|
{
|
|
return qobject_cast<const Scene_image_item*>(item);
|
|
}
|
|
|
|
bool Io_image_plugin::loadDirectory(const QString& dirname,
|
|
const Directory_extension_type ext)
|
|
{
|
|
#ifndef CGAL_USE_VTK
|
|
QApplication::restoreOverrideCursor();
|
|
CGAL::Three::Three::warning("VTK is required to read DCM and BMP files");
|
|
CGAL_USE(dirname);
|
|
CGAL_USE(ext);
|
|
return false;
|
|
#else
|
|
QFileInfo fileinfo;
|
|
fileinfo.setFile(dirname);
|
|
bool result = true;
|
|
if(!fileinfo.isReadable())
|
|
{
|
|
QMessageBox::warning(mw, mw->windowTitle(),
|
|
tr("Cannot read directory <tt>%1</tt>!").arg(dirname));
|
|
CGAL::Three::Three::warning(tr("Opening of directory %1 failed!").arg(dirname));
|
|
result = false;
|
|
}
|
|
else
|
|
{
|
|
// Get display precision
|
|
QDialog dialog;
|
|
ui.setupUi(&dialog);
|
|
|
|
connect(ui.buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
|
|
connect(ui.buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
|
|
connect(ui.imageType, SIGNAL(currentIndexChanged(int)),
|
|
this, SLOT(on_imageType_changed(int)));
|
|
|
|
// Add precision values to the dialog
|
|
for ( int i=1 ; i<9 ; ++i )
|
|
{
|
|
QString s = tr("1:%1").arg(i*i*i);
|
|
ui.precisionList->addItem(s);
|
|
}
|
|
|
|
// Adds Image type
|
|
ui.imageType->addItem(QString("Segmented image"));
|
|
ui.imageType->addItem(QString("Gray-level image"));
|
|
|
|
// Open window
|
|
QApplication::restoreOverrideCursor();
|
|
int return_code = dialog.exec();
|
|
if(return_code != QDialog::Accepted)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
|
QApplication::processEvents();
|
|
|
|
bool smooth = ui.smoothImage->isChecked();
|
|
|
|
Image *image = createDirectoryImage(dirname, ext, smooth);
|
|
if(image->image() == nullptr)
|
|
{
|
|
QMessageBox::warning(mw, mw->windowTitle(), tr("Error opening directory <tt>%1/</tt>!").arg(dirname));
|
|
CGAL::Three::Three::warning(tr("Opening of directory %1/ failed!").arg(dirname));
|
|
result = false;
|
|
}
|
|
else
|
|
{
|
|
CGAL::Three::Three::information(tr("Directory %1/ successfully opened.").arg(dirname));
|
|
}
|
|
|
|
if(result)
|
|
{
|
|
// Get the image type
|
|
QString type = ui.imageType->currentText();
|
|
Scene_image_item* image_item;
|
|
if(type == "Gray-level image")
|
|
{
|
|
// Create planes
|
|
image_item = new Scene_image_item(image,125, true);
|
|
image_item->setName(fileinfo.baseName());
|
|
msgBox.setText("Planes created : 0/3");
|
|
msgBox.setStandardButtons(QMessageBox::NoButton);
|
|
msgBox.show();
|
|
|
|
scene->addItem(image_item);
|
|
createPlanes(image_item);
|
|
}
|
|
else
|
|
{
|
|
// Get selected precision
|
|
int voxel_scale = ui.precisionList->currentIndex() + 1;
|
|
|
|
image_item = new Scene_image_item(image,voxel_scale, false);
|
|
image_item->setName(fileinfo.baseName());
|
|
scene->addItem(image_item);
|
|
}
|
|
}
|
|
}
|
|
|
|
QApplication::restoreOverrideCursor();
|
|
return result;
|
|
#endif
|
|
}
|
|
|
|
Image* Io_image_plugin::createDirectoryImage(const QString& dirname,
|
|
const Directory_extension_type ext,
|
|
const bool smooth)
|
|
{
|
|
Image* image = nullptr;
|
|
|
|
#ifndef CGAL_USE_VTK
|
|
CGAL::Three::Three::warning("VTK is required to read DCM and BMP files");
|
|
CGAL_USE(dirname);
|
|
CGAL_USE(ext);
|
|
CGAL_USE(smooth);
|
|
#else
|
|
auto create_image = [&](auto&& reader) -> void
|
|
{
|
|
auto executive = vtkDemandDrivenPipeline::SafeDownCast(reader->GetExecutive());
|
|
if(executive)
|
|
executive->SetReleaseDataFlag(0, 0); // where 0 is the port index
|
|
|
|
vtkImageData* vtk_image = nullptr;
|
|
vtkNew<vtkImageGaussianSmooth> smoother; // must be here because it will own the vtk image
|
|
|
|
if(smooth)
|
|
{
|
|
smoother->SetStandardDeviations(1., 1., 1.);
|
|
smoother->SetInputConnection(reader->GetOutputPort());
|
|
smoother->Update();
|
|
vtk_image = smoother->GetOutput();
|
|
}
|
|
else
|
|
{
|
|
reader->Update();
|
|
vtk_image = reader->GetOutput();
|
|
}
|
|
|
|
vtk_image->Print(std::cerr);
|
|
|
|
*image = CGAL::IO::read_vtk_image_data(vtk_image); // copy the image data
|
|
};
|
|
|
|
image = new Image;
|
|
if(ext == Directory_extension_type::DCM)
|
|
{
|
|
vtkNew<vtkDICOMImageReader> dicom_reader;
|
|
dicom_reader->SetDirectoryName(dirname.toUtf8());
|
|
create_image(dicom_reader);
|
|
}
|
|
else
|
|
{
|
|
CGAL_assertion(ext == Directory_extension_type::BMP);
|
|
|
|
// vtkBMPReader does not provide SetDirectoryName()...
|
|
std::vector<boost::filesystem::path> paths;
|
|
vtkStringArray* files = vtkStringArray::New();
|
|
boost::filesystem::path p(dirname.toUtf8().data());
|
|
for(boost::filesystem::directory_entry& x : boost::filesystem::directory_iterator(p))
|
|
{
|
|
std::string s = x.path().string();
|
|
if(CGAL::IO::internal::get_file_extension(s) != "bmp")
|
|
continue;
|
|
|
|
paths.push_back(x.path());
|
|
}
|
|
|
|
// boost::filesystem::directory_iterator does not guarantee a sorted order
|
|
std::sort(std::begin(paths), std::end(paths));
|
|
|
|
for(const boost::filesystem::path& p : paths)
|
|
files->InsertNextValue(p.string());
|
|
|
|
if(files->GetSize() == 0)
|
|
return image;
|
|
|
|
vtkNew<vtkBMPReader> bmp_reader;
|
|
bmp_reader->SetFileNames(files);
|
|
create_image(bmp_reader);
|
|
}
|
|
#endif
|
|
|
|
return image;
|
|
}
|
|
|
|
#include "Io_image_plugin.moc"
|