diff --git a/CGAL_ImageIO/include/CGAL/IO/read_vtk_image_data.h b/CGAL_ImageIO/include/CGAL/IO/read_vtk_image_data.h index ccce66f3ca6..0492e2acc6a 100644 --- a/CGAL_ImageIO/include/CGAL/IO/read_vtk_image_data.h +++ b/CGAL_ImageIO/include/CGAL/IO/read_vtk_image_data.h @@ -69,26 +69,57 @@ read_vtk_image_data(vtkImageData* vtk_image, Image_3::Own owning = Image_3::OWN_ image->ty = static_cast(offset[1]); image->tz = static_cast(offset[2]); image->endianness = ::_getEndianness(); + int vtk_type = vtk_image->GetScalarType(); if(vtk_type == VTK_SIGNED_CHAR) vtk_type = VTK_CHAR; - if(vtk_type < 0 || vtk_type > VTK_DOUBLE) - vtk_type = VTK_DOUBLE; - const VTK_to_ImageIO_type_mapper& imageio_type = - VTK_to_ImageIO_type[vtk_type]; + if(vtk_type < 0 || vtk_type > VTK_DOUBLE) vtk_type = VTK_DOUBLE; + const VTK_to_ImageIO_type_mapper& imageio_type = VTK_to_ImageIO_type[vtk_type]; image->wdim = imageio_type.wdim; image->wordKind = imageio_type.wordKind; image->sign = imageio_type.sign; + + const int cn = vtk_image->GetNumberOfScalarComponents(); + if (!vtk_image->GetPointData() || !vtk_image->GetPointData()->GetScalars()) { ::_freeImage(image); return Image_3(); } + + // If there is more than a scalar per point, vtk_image->data is not immediately + // interpretable in Image_3->data + CGAL_assertion(owning == Image_3::OWN_THE_DATA || cn == 1); + CGAL_assertion(vtk_image->GetPointData()->GetScalars()->GetNumberOfTuples() == dims[0]*dims[1]*dims[2]); + if(owning == Image_3::OWN_THE_DATA) { - image->data = ::ImageIO_alloc(dims[0]*dims[1]*dims[2]*image->wdim); - // std::cerr << "GetNumberOfTuples()=" << vtk_image->GetPointData()->GetScalars()->GetNumberOfTuples() - // << "\nimage->size()=" << dims[0]*dims[1]*dims[2] - // << "\nwdim=" << image->wdim << '\n'; - vtk_image->GetPointData()->GetScalars()->ExportToVoidPointer(image->data); + int dims_n = dims[0]*dims[1]*dims[2]; + image->data = ::ImageIO_alloc(dims_n * image->wdim); + + // std::cerr << "GetNumberOfTuples() = " << vtk_image->GetPointData()->GetScalars()->GetNumberOfTuples() << "\n" + // << "components = " << cn << "\n" + // << "wdim = " << image->wdim << "\n" + // << "image->size() = " << dims_n << std::endl; + + if(cn == 1) { + vtk_image->GetPointData()->GetScalars()->ExportToVoidPointer(image->data); + } else { + std::cerr << "Warning: input has " << cn << " components; only the value of the first component will be used." << std::endl; + CGAL_assertion(cn >= 3); // if it's more than 1, it needs to be at least 3 + + // cast the data void pointers to make it possible to do pointer arithmetic + char* src = static_cast(vtk_image->GetPointData()->GetScalars()->GetVoidPointer(0)); + char* dest = static_cast(image->data); + + for(int i=0; iwdim because we casted to char* and not the actual data type + memcpy(dest + image->wdim*i, src + cn*image->wdim*i, image->wdim); + + // Check that we are not discarding useful data (i.e., green & blue are identical to red) + CGAL_assertion(memcmp(src + cn*image->wdim*i, src + cn*image->wdim*i + image->wdim, image->wdim) == 0); + CGAL_assertion(memcmp(src + cn*image->wdim*i, src + cn*image->wdim*i + 2*image->wdim, image->wdim) == 0); + } + } } else { image->data = vtk_image->GetPointData()->GetScalars()->GetVoidPointer(0); } diff --git a/CGAL_ImageIO/include/CGAL/Image_3.h b/CGAL_ImageIO/include/CGAL/Image_3.h index f6b186ef36c..e653e9de83b 100644 --- a/CGAL_ImageIO/include/CGAL/Image_3.h +++ b/CGAL_ImageIO/include/CGAL/Image_3.h @@ -87,7 +87,7 @@ public: protected: Image_shared_ptr image_ptr; - // implementation in src/CGAL_ImageIO/Image_3.cpp + // implementation in Image_3_impl.h bool private_read(_image* im, Own own_the_data = OWN_THE_DATA); public: diff --git a/Mesh_3/doc/Mesh_3/CGAL/Image_3.h b/Mesh_3/doc/Mesh_3/CGAL/Image_3.h index 415638914a4..3f5bce1ddcb 100644 --- a/Mesh_3/doc/Mesh_3/CGAL/Image_3.h +++ b/Mesh_3/doc/Mesh_3/CGAL/Image_3.h @@ -14,7 +14,7 @@ public: /// The default-constructor. The object is invalid until a call to `read()`. Image_3(); - /// Open an 3D image file. + /// Open a 3D image file. /// /// Returns `true` if the file was sucessfully loaded. bool read(const char* file); diff --git a/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/Image_res_dialog.ui b/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/Image_res_dialog.ui index 066201cbed7..8e8c915e77f 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/Image_res_dialog.ui +++ b/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/Image_res_dialog.ui @@ -6,8 +6,8 @@ 0 0 - 421 - 370 + 561 + 347 @@ -19,24 +19,8 @@ Load a 3D image - - - - - Qt::Vertical - - - - 20 - 0 - - - - - - - - + + Qt::Horizontal @@ -46,33 +30,31 @@ - - - - Please choose the image &type - - - imageType - - - - + - Drawing settings for a segment image + + + 0 + + + 0 + + + 0 + - - - - - false - - - - - + + + 0 + + + 6 + + + 11 @@ -80,20 +62,23 @@ - 1:x means that x voxels of the original image are represented by 1 cube in the drawn image + <html><head/><body><p align="center"><span style=" font-size:10pt;">1:x means that x voxels of the original image are represented by 1 cube in the drawn image</span></p></body></html> true - - + + + + true + 1 - Please choose the image drawing &precision + <html><head/><body><p align="right">Segmented image drawing &amp;precision </p></body></html> true @@ -103,11 +88,81 @@ + + + + false + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <html><head/><body><p align="right">Image &amp;type</p></body></html> + + + imageType + + + + + + + Qt::Horizontal + + + + + + + Smooth image data + + + + + + + + + + true + + + + + + diff --git a/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/Io_image_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/Io_image_plugin.cpp index f74562e6e58..0819581709e 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/Io_image_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/Io_image_plugin.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -44,24 +45,19 @@ #include #include -#include -#include - -#include -#include - #include #include #include "Raw_image_dialog.h" #include -#include -#include + #ifdef CGAL_USE_VTK #include #include +#include #include #include +#include #include #include #include @@ -70,44 +66,58 @@ #include +#include +#include +#include + +#include +#include +#include +#include +#include + // 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 { +struct IntConverter +{ std::pair min_max; - int operator()(float f) { + 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){ + if (s - floor(s) >= 0.5) return int(s)+1 + min_max.first; - } - else{ + else return s + float(min_max.first); - } } }; -struct DoubleConverter { +struct DoubleConverter +{ std::pair min_max; - float operator()(float f) { + float operator()(float f) + { float s = f * (min_max.second - min_max.first); return s + min_max.first; } }; -class PixelReader : public QObject +class PixelReader + : public QObject { Q_OBJECT + public Q_SLOTS: - void update(const QMouseEvent *e) { - getPixel(e->pos()); - } + void update(const QMouseEvent *e) { getPixel(e->pos()); } + Q_SIGNALS: void x(QString); + public: void setIC(const IntConverter& x) { ic = x; fc = boost::optional(); } void setFC(const DoubleConverter& x) { fc = x; ic = boost::optional(); } @@ -117,7 +127,8 @@ private: boost::optional ic; boost::optional fc; Viewer_interface* viewer; - void getPixel(const QPoint& e) { + 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 )); @@ -127,48 +138,59 @@ private: } }; - -class Plane_slider : public QSlider +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) { + 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())); + connect(frame, SIGNAL(manipulated()), this, SLOT(updateCutPlane())); } public Q_SLOTS: void updateCutPlane() { - ready_to_cut = true; - QTimer::singleShot(0,this,SLOT(updateValue())); + 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; + v2 += offset; frame->setTranslationWithConstraint(v2); scene->itemChanged(id); Q_EMIT realChange(this->value() / scale); ready_to_move = false; } - void updateValue() { + + 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; + 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; @@ -181,7 +203,9 @@ public Q_SLOTS: ready_to_move = true; QTimer::singleShot(0,this,SLOT(setFramePosition())); } + unsigned int getScale() const { return scale; } + Q_SIGNALS: void realChange(int); @@ -191,12 +215,19 @@ private: 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, @@ -208,33 +239,46 @@ class Io_image_plugin : Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.IOPluginInterface/1.90" FILE "io_image_plugin.json") public: - - bool applicable(QAction*) const override{ + bool applicable(QAction*) const override + { return qobject_cast(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 { + 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 (for DCM files)", mw); + + QAction *actionLoadDCM = new QAction("Open Directory (DCM files)", mw); connect(actionLoadDCM, SIGNAL(triggered()), this, SLOT(on_actionLoadDCM_triggered())); - if(planeSwitch) { + + 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(); @@ -242,19 +286,19 @@ public: this, SLOT(connectNewViewer(QObject*))); QMenu* menuFile = mw->findChild("menuFile"); - if ( nullptr != menuFile ) + if(nullptr != menuFile ) { QList menuFileActions = menuFile->actions(); // Look for action just after "Load..." action QAction* actionAfterLoad = nullptr; - for ( QList::iterator it_action = menuFileActions.begin(), - end = menuFileActions.end() ; it_action != end ; ++ it_action ) //Q_FOREACH( QAction* action, menuFileActions) + for(QList::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 Plugin") ) + if(NULL != *it_action && (*it_action)->text().contains("Load...")) { ++it_action; - if ( it_action != end && NULL != *it_action ) + if(it_action != end && NULL != *it_action) { actionAfterLoad = *it_action; } @@ -262,29 +306,35 @@ public: } // Insert "Load implicit function" action - if ( nullptr != actionAfterLoad ) + if(nullptr != actionAfterLoad) { - menuFile->insertAction(actionAfterLoad,actionLoadDCM); + menuFile->insertAction(actionAfterLoad, actionLoadDCM); + menuFile->insertAction(actionAfterLoad, actionLoadBMP); } } } - QList actions() const override{ + + QList actions() const override + { return QList() << planeSwitch; } + virtual void closure() override { - QDockWidget* controlDockWidget = mw->findChild("volumePlanesControl"); - if(controlDockWidget) - controlDockWidget->hide(); + QDockWidget* controlDockWidget = mw->findChild("volumePlanesControl"); + if(controlDockWidget) + controlDockWidget->hide(); } - Io_image_plugin() : planeSwitch(nullptr) {} + + Io_image_plugin() : planeSwitch(nullptr) { } QString nameFilters() const override; bool canLoad(QFileInfo) const override; - QList load(QFileInfo fileinfo, bool& ok, bool add_to_scene=true) override; + QList load(QFileInfo fileinfo, bool& ok, bool add_to_scene = true) override; bool canSave(const CGAL::Three::Scene_item*) override; - bool save(QFileInfo fileinfo, QList& items ) override{ + bool save(QFileInfo fileinfo, QList& items ) override + { Scene_item* item = items.front(); const Scene_image_item* im_item = qobject_cast(item); @@ -294,16 +344,17 @@ public: items.pop_front(); return ok; } - bool isDefaultLoader(const Scene_item* item) const override{ + + bool isDefaultLoader(const Scene_item* item) const override + { if(qobject_cast(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)); @@ -334,7 +385,6 @@ public Q_SLOTS: int i = s.toInt(); z_slider->setValue(i*qobject_cast(z_slider)->getScale()); z_slider->sliderMoved(i); - } void on_imageType_changed(int index) @@ -344,38 +394,50 @@ public Q_SLOTS: else ui.groupBox->setVisible(false); } - void selectPlanes() { + 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) { + for(int i = 0; i < scene->numberOfEntries(); ++i) + { Scene_image_item* tmp = qobject_cast(scene->item(i)); - if(tmp != nullptr){ + if(tmp != nullptr) seg_items.push_back(tmp); - } } - if(seg_items.empty()) { + + 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 { + } + else + { QList items; for(std::vector< Scene_image_item* >::const_iterator it = seg_items.begin(); - it != seg_items.end(); ++it) { + 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) { + 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. @@ -386,15 +448,18 @@ public Q_SLOTS: } } - void addVP(Volume_plane_thread* thread) { + 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': @@ -445,20 +510,22 @@ public Q_SLOTS: default: break; } + std::vector::iterator it = std::find(threads.begin(), threads.end(), thread); - // this slot has been connected to a thread that hasn't been - // registered here. + // 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(scene->item(intersection_id)); - if(!intersection) { + 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* p = dynamic_cast< Volume_plane* >(plane)) { @@ -468,36 +535,41 @@ public Q_SLOTS: } else if(Volume_plane* p = dynamic_cast< Volume_plane* >(plane)) { intersection->setZ(p); } + connect(plane, SIGNAL(planeDestructionIncoming(Volume_plane_interface*)), intersection, SLOT(planeRemoved(Volume_plane_interface*))); } - void on_actionLoadDCM_triggered() + 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); + 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()) { + if(!dir.isEmpty()) + { QFileInfo fileinfo(dir); - if (fileinfo.isDir() && fileinfo.isReadable()) + if(fileinfo.isDir() && fileinfo.isReadable()) { - settings.setValue("Open directory", - fileinfo.absoluteDir().absolutePath()); + settings.setValue("Open directory", fileinfo.absoluteDir().absolutePath()); QApplication::setOverrideCursor(Qt::WaitCursor); QApplication::processEvents(); - loadDCM(dir); - QApplication::restoreOverrideCursor(); + + 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) @@ -509,11 +581,14 @@ public Q_SLOTS: 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; @@ -524,7 +599,9 @@ private: CGAL::Three::Scene_group_item* group; std::vector threads; - struct Controls{ + + struct Controls + { CGAL::Three::Scene_item* group; CGAL::Three::Scene_item* x_item; CGAL::Three::Scene_item* y_item; @@ -533,16 +610,21 @@ private: int y_value; int z_value; }; - Controls *current_control; + + Controls* current_control; QMap group_map; unsigned int intersection_id; - bool loadDCM(QString filename); - Image* createDCMImage(QString dirname); - QLayout* createOrGetDockLayout() { - QLayout* layout = nullptr; - QDockWidget* controlDockWidget = mw->findChild("volumePlanesControl");; - if(!controlDockWidget) { + 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("volumePlanesControl"); + + if(!controlDockWidget) + { controlDockWidget = new QDockWidget(mw); controlDockWidget->setObjectName("volumePlanesControl"); QWidget* content = new QWidget(controlDockWidget); @@ -568,7 +650,9 @@ private: controlDockWidget->setWidget(content); controlDockWidget->hide(); - } else { + } + else + { layout = controlDockWidget->findChild("vpSliderLayout"); controlDockWidget->show(); controlDockWidget->raise(); @@ -577,7 +661,8 @@ private: return layout; } - void createPlanes(Scene_image_item* seg_img) { + void createPlanes(Scene_image_item* seg_img) + { QApplication::setOverrideCursor(Qt::WaitCursor); //Control widgets creation QLayout* layout = createOrGetDockLayout(); @@ -670,11 +755,13 @@ private: 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)) { + if(!(seg_img == nullptr)) + { const CGAL::Image_3* img = seg_img->image(); CGAL_IMAGE_IO_CASE(img->image(), this->launchAdders(seg_img, seg_img->name())) @@ -688,15 +775,17 @@ private: this->intersection_id = scene->addItem(i); scene->changeGroup(i, group); group->lockChild(i); - } else { + } + else + { QMessageBox::warning(mw, tr("Something went wrong"), tr("Selected a suitable Object but couldn't get an image pointer.")); return; } } - template - void launchAdders(Scene_image_item* seg_img, const QString& name) { + 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(); @@ -711,9 +800,9 @@ private: Volume_plane *y_item = new Volume_plane(img->image()->tx,img->image()->ty, img->image()->tz); Volume_plane *z_item = new Volume_plane(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->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")); @@ -733,6 +822,7 @@ private: connect(group, SIGNAL(aboutToBeDestroyed()), this, SLOT(erase_group())); scene->addItem(group); + Controls c; c.group = group; c.x_item = x_item; @@ -761,19 +851,23 @@ private: first_offset = Three::mainViewer()->offset(); } + template - void switchReaderConverter(std::pair minmax) { + void switchReaderConverter(std::pair minmax) + { switchReaderConverter(minmax, typename boost::is_integral::type()); } template - void switchReaderConverter(std::pair minmax, boost::true_type) { + void switchReaderConverter(std::pair minmax, boost::true_type) + { // IntConverter IntConverter x = { minmax }; pxr_.setIC(x); } template - void switchReaderConverter(std::pair minmax, boost::false_type) { + void switchReaderConverter(std::pair minmax, boost::false_type) + { // IntConverter DoubleConverter x = { minmax }; pxr_.setFC(x); } @@ -789,7 +883,7 @@ private Q_SLOTS: void update_msgBox() { static int nbPlanes =0; - nbPlanes ++; + ++nbPlanes; msgBox.setText(QString("Planes created : %1/3").arg(nbPlanes)); if(nbPlanes == 3) { @@ -801,6 +895,7 @@ private Q_SLOTS: scene->item(i)->invalidateOpenGLBuffers(); } } + msgBox.hide(); nbPlanes = 0; QApplication::restoreOverrideCursor(); @@ -809,11 +904,9 @@ private Q_SLOTS: // Avoids the segfault after the deletion of an item void erase_group() { - CGAL::Three::Scene_group_item* group_item = qobject_cast(sender()); if(group_item) { - Q_FOREACH(CGAL::Three::Scene_item* key, group_map.keys()) { if(group_map[key].group == group_item) @@ -826,38 +919,7 @@ private Q_SLOTS: } } } - //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(sender()); - if(img_item) - { - Scene_group_item* group = qobject_cast(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 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()) { @@ -865,11 +927,49 @@ private Q_SLOTS: connect_controls(id); } } + + // destroy planes on image deletion + void on_img_detroyed() + { + Scene_image_item* img_item = qobject_cast(sender()); + if(img_item) + { + Scene_group_item* group = qobject_cast(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 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(); @@ -878,15 +978,18 @@ private Q_SLOTS: 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(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); @@ -902,12 +1005,14 @@ private Q_SLOTS: x_box->addWidget(x_cubeLabel); show_sliders &= qobject_cast(sel_itm)->image()->xdim() > 1; } + //y line if(c.y_item != nullptr) { Volume_plane_interface* y_plane = qobject_cast(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); @@ -922,12 +1027,14 @@ private Q_SLOTS: y_box->addWidget(y_cubeLabel); show_sliders &= qobject_cast(sel_itm)->image()->ydim() > 1; } + // z line if(c.z_item != nullptr) { Volume_plane_interface* z_plane = qobject_cast(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); @@ -947,7 +1054,8 @@ private Q_SLOTS: y_control->setEnabled(show_sliders); z_control->setEnabled(show_sliders); } -//Keeps the position of the planes for the next time + + // Keeps the position of the planes for the next time void set_value() { current_control->x_value = x_slider->value(); @@ -973,19 +1081,18 @@ private Q_SLOTS: if(group_map.isEmpty()) z_control->hide(); } - }; - -QString Io_image_plugin::nameFilters() const { +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) ;; " "NRRD image files (*.nrrd)"); } - -bool Io_image_plugin::canLoad(QFileInfo) const { +bool Io_image_plugin::canLoad(QFileInfo) const +{ return true; } @@ -994,14 +1101,18 @@ 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; xxdim(); ++x) - for(std::size_t y = 0; yydim(); ++y) + + // convert image from double to float + for(std::size_t x = 0; xxdim(); ++x) { + for(std::size_t y = 0; yydim(); ++y) { for(std::size_t z = 0; zzdim(); ++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; @@ -1015,8 +1126,8 @@ Io_image_plugin::load(QFileInfo fileinfo, bool& ok, bool add_to_scene) QApplication::restoreOverrideCursor(); Image* image = new Image; - //read a nrrd file - if (fileinfo.suffix() == "nrrd") + // read a nrrd file + if(fileinfo.suffix() == "nrrd") { #ifdef CGAL_USE_VTK vtkNew reader; @@ -1026,13 +1137,13 @@ Io_image_plugin::load(QFileInfo fileinfo, bool& ok, bool add_to_scene) vtk_image->Print(std::cerr); *image = CGAL::IO::read_vtk_image_data(vtk_image); // copy the image data #else - CGAL::Three::Three::warning("You need VTK to read a NRRD file"); + CGAL::Three::Three::warning("VTK is required to read NRRD files"); delete image; return QList(); #endif } - //read a sep file + // read a sep file else if (fileinfo.suffix() == "H" || fileinfo.suffix() == "HH") { CGAL::SEP_to_ImageIO reader(fileinfo.filePath().toUtf8().data()); @@ -1041,94 +1152,104 @@ Io_image_plugin::load(QFileInfo fileinfo, bool& ok, bool add_to_scene) } else if(fileinfo.suffix() != "H" && fileinfo.suffix() != "HH" && - !image->read(fileinfo.filePath().toUtf8())) + !image->read(fileinfo.filePath().toUtf8())) + { + QMessageBox qmb(QMessageBox::NoIcon, + "Raw Dialog", + tr("Error with file %1:\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) { - QMessageBox qmb(QMessageBox::NoIcon, - "Raw Dialog", - tr("Error with file %1:\n" - "unknown file format!\n" - "\n" - "Open it as a raw image?").arg(fileinfo.fileName()), - QMessageBox::Yes|QMessageBox::No); + 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(); - 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()) + 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(image); + break; + case WK_FIXED: + { + switch(raw_dialog.image_word_size()) { - case WK_FLOAT: + case 2: is_gray = true; - convert(image); + convert(image); break; - case WK_FIXED: - { - switch(raw_dialog.image_word_size()) - { - case 2: - is_gray = true; - convert(image); - break; - case 4: - is_gray = true; - convert(image); - break; - default: - is_gray = false; - break; - } + case 4: + is_gray = true; + convert(image); break; - } default: + is_gray = false; 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; + break; } - }else { + 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 { + } + else + { success = false; } - if(!success){ - ok = false; - delete image; - return QList(); - } } + else + { + success = false; + } + + if(!success) + { + ok = false; + delete image; + return QList(); + } + } // Get display precision QDialog dialog; @@ -1141,18 +1262,19 @@ Io_image_plugin::load(QFileInfo fileinfo, bool& ok, bool add_to_scene) dialog.setWindowFlags(Qt::Dialog|Qt::CustomizeWindowHint|Qt::WindowCloseButtonHint); // Add precision values to the dialog - for ( int i=1 ; i<9 ; ++i ) + for(int i=1 ; i<9; ++i) { QString s = tr("1:%1").arg(i*i*i); ui.precisionList->addItem(s); } - //Adds Image type + // 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) @@ -1172,9 +1294,13 @@ Io_image_plugin::load(QFileInfo fileinfo, bool& ok, bool add_to_scene) 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") { @@ -1188,10 +1314,14 @@ Io_image_plugin::load(QFileInfo fileinfo, bool& ok, bool add_to_scene) 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() << image_item; } @@ -1200,10 +1330,15 @@ bool Io_image_plugin::canSave(const CGAL::Three::Scene_item* item) return qobject_cast(item); } -bool Io_image_plugin::loadDCM(QString dirname) +bool Io_image_plugin::loadDirectory(const QString& dirname, + const Directory_extension_type ext) { +#ifndef CGAL_USE_VTK QApplication::restoreOverrideCursor(); -#ifdef CGAL_USE_VTK + CGAL::Three::Three::warning("VTK is required to read DCM and BMP files"); + CGAL_USE(dirname); + return false; +#else QFileInfo fileinfo; fileinfo.setFile(dirname); bool result = true; @@ -1225,7 +1360,6 @@ bool Io_image_plugin::loadDCM(QString dirname) 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 ) { @@ -1233,11 +1367,10 @@ bool Io_image_plugin::loadDCM(QString dirname) ui.precisionList->addItem(s); } - //Adds Image type + // 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(); @@ -1245,99 +1378,136 @@ bool Io_image_plugin::loadDCM(QString dirname) { return false; } + QApplication::setOverrideCursor(Qt::WaitCursor); QApplication::processEvents(); - // Get selected precision - int voxel_scale = ui.precisionList->currentIndex() + 1; + bool smooth = ui.smoothImage->isChecked(); - //Get the image type - QString type = ui.imageType->currentText(); - Scene_image_item* image_item; - if(type == "Gray-level image") + Image *image = createDirectoryImage(dirname, ext, smooth); + if(image->image() == nullptr) { - - Image *image = createDCMImage(dirname); - if(image->image() == nullptr) - { - QMessageBox::warning(mw, mw->windowTitle(), - tr("Error with file %1/:\nunknown file format!").arg(dirname)); - CGAL::Three::Three::warning(tr("Opening of file %1/ failed!").arg(dirname)); - result = false; - } - else - { - CGAL::Three::Three::information(tr("File %1/ successfully opened.").arg(dirname)); - } - if(result) - { - //Create planes - image_item = new Scene_image_item(image,125, true); - msgBox.setText("Planes created : 0/3"); - msgBox.setStandardButtons(QMessageBox::NoButton); - msgBox.show(); - createPlanes(image_item); - image_item->setName(fileinfo.baseName()); - scene->addItem(image_item); - } + QMessageBox::warning(mw, mw->windowTitle(), tr("Error opening directory %1/!").arg(dirname)); + CGAL::Three::Three::warning(tr("Opening of directory %1/ failed!").arg(dirname)); + result = false; } else { - Image *image = createDCMImage(dirname); - if(image->image() == nullptr) + 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") { - QMessageBox::warning(mw, mw->windowTitle(), - tr("Error with file %1/:\nunknown file format!").arg(dirname)); - CGAL::Three::Three::warning(tr("Opening of file %1/ failed!").arg(dirname)); - result = false; + // 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 { - CGAL::Three::Three::information(tr("File %1/ successfully opened.").arg(dirname)); - } - if(result) - { + // 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; -#else - CGAL::Three::Three::warning("You need VTK to read a DCM file"); - CGAL_USE(dirname); - return false; #endif } -Image* Io_image_plugin::createDCMImage(QString dirname) + +Image* Io_image_plugin::createDirectoryImage(const QString& dirname, + const Directory_extension_type ext, + const bool smooth) { Image* image = nullptr; -#ifdef CGAL_USE_VTK - vtkNew dicom_reader; - dicom_reader->SetDirectoryName(dirname.toUtf8()); - auto executive = - vtkDemandDrivenPipeline::SafeDownCast(dicom_reader->GetExecutive()); - if (executive) - { - executive->SetReleaseDataFlag(0, 0); // where 0 is the port index - } - - vtkNew smoother; - smoother->SetStandardDeviations(1., 1., 1.); - smoother->SetInputConnection(dicom_reader->GetOutputPort()); - smoother->Update(); - auto vtk_image = smoother->GetOutput(); - vtk_image->Print(std::cerr); - image = new Image; - *image = CGAL::IO::read_vtk_image_data(vtk_image); // copy the image data -#else - CGAL::Three::Three::warning("You need VTK to read a DCM file"); +#ifndef CGAL_USE_VTK + CGAL::Three::Three::warning("VTK is required to read DCM and BMP files"); CGAL_USE(dirname); -#endif - return image; + CGAL_USE(ext); +#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 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 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 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 bmp_reader; + bmp_reader->SetFileNames(files); + create_image(bmp_reader); + } +#endif + + return image; } #include "Io_image_plugin.moc"