diff --git a/src/meshlabplugins/io_epoch/epoch_camera.cpp b/src/meshlabplugins/io_epoch/epoch_camera.cpp new file mode 100644 index 000000000..aaa16097a --- /dev/null +++ b/src/meshlabplugins/io_epoch/epoch_camera.cpp @@ -0,0 +1,88 @@ +#include +#include +#include +#include + +using namespace vcg; +using namespace std; + +#include "radial_distortion.h" +#include "epoch_camera.h" + + +/* +Epoch Camera + + +*/ +// This function take in input a point in image space (e.g. with coords in range [0..1024]x[0..768] +// a depth value and it returns the point in absolute 3D coords +// +void EpochCamera::DepthTo3DPoint(double x, double y, double depth, Point3d &M) const +{ + Point3d m_temp = Kinv * Point3d(x,y,1); + + double oldx, oldy; + rd.ComputeOldXY(m_temp[0] / m_temp[2], m_temp[1] / m_temp[2], oldx, oldy); + + m_temp=Point3d(oldx,oldy,1); + Point3d fp=t; + Point3d end = TRinv*m_temp; + Point3d dir =fp-end; + dir.Normalize(); + M = fp-dir*depth; +} + + + + +bool EpochCamera::Open(const char *filename) +{ + FILE *fp=fopen(filename,"rb"); + if(!fp) return false; + + fscanf(fp,"%lf %lf %lf",&(K[0][0]),&(K[0][1]),&(K[0][2])); + fscanf(fp,"%lf %lf %lf",&(K[1][0]),&(K[1][1]),&(K[1][2])); + fscanf(fp,"%lf %lf %lf",&(K[2][0]),&(K[2][1]),&(K[2][2])); + + k.resize(3); + fscanf(fp,"%lf %lf %lf",&(k[0]),&(k[1]),&(k[2])); + + fscanf(fp,"%lf %lf %lf",&(R[0][0]),&(R[0][1]),&(R[0][2])); + fscanf(fp,"%lf %lf %lf",&(R[1][0]),&(R[1][1]),&(R[1][2])); + fscanf(fp,"%lf %lf %lf",&(R[2][0]),&(R[2][1]),&(R[2][2])); + + fscanf(fp,"%lf %lf %lf",&(t[0]),&(t[1]),&(t[2])); + + fscanf(fp,"%i %i",&width,&height); + + fclose(fp); + Kinv=Inverse(K); + + rd.SetParameters(k); + + // TR = [R | -Rt] 4x4 matrix with upperleft a 3x3 rotation + // on the right the rotated translation -Rt and 0001 in the + // lower line. + + R.transposeInPlace(); + #ifndef VCG_USE_EIGEN + for(int i=0;i<3;++i) + for(int j=0;j<3;++j) + TR[i][j]= R[i][j]; + #else + TR.corner<3,3>(Eigen::TopLeft) = R.transpose(); + #endif + + Point3d rt= R*(-t); + + for(int i=0;i<3;++i) + TR[i][3]=rt[i]; + + for(int j=0;j<3;++j) + TR[3][j]=0; + + TR[3][3]=1; + TRinv=Inverse(TR); + return true; +} diff --git a/src/meshlabplugins/io_epoch/epoch_camera.h b/src/meshlabplugins/io_epoch/epoch_camera.h new file mode 100644 index 000000000..9be2a07f5 --- /dev/null +++ b/src/meshlabplugins/io_epoch/epoch_camera.h @@ -0,0 +1,24 @@ + +namespace vcg +{ +class EpochCamera +{ +public: + Matrix33d K; // parametri intriseci camera + Matrix33d Kinv; + + std::vector k; + Matrix33d R; + Matrix44d TR; // [R | -Rt] e.g. la matrice in cui + Matrix44d TRinv; + Point3d t; + int width, height; + + RadialDistortion rd; + + void DepthTo3DPoint(double x, double y, double depth, Point3d &M) const; + + bool Open(const char * filename); +}; + +} \ No newline at end of file diff --git a/src/meshlabplugins/io_epoch/epoch_io.cpp b/src/meshlabplugins/io_epoch/epoch_io.cpp new file mode 100644 index 000000000..09121426f --- /dev/null +++ b/src/meshlabplugins/io_epoch/epoch_io.cpp @@ -0,0 +1,781 @@ +/**************************************************************************** +* MeshLab o o * +* An extendible mesh processor o o * +* _ O _ * +* Copyright(C) 2005, 2006 \/)\/ * +* Visual Computing Lab /\/| * +* ISTI - Italian National Research Council | * +* \ * +* All rights reserved. * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License (http://www.gnu.org/licenses/gpl.txt) * +* for more details. * +* * +****************************************************************************/ +/**************************************************************************** + History + + $Log$ + Revision 1.22 2008/04/04 14:08:07 cignoni + Solved namespace ambiguities caused by the removal of a silly 'using namespace' in meshmodel.h + + Revision 1.21 2008/03/06 08:20:50 cignoni + updated to the new histogram + + Revision 1.20 2008/02/12 21:59:02 cignoni + removed mask bug and added scaling of maps + + Revision 1.19 2007/11/26 07:35:26 cignoni + Yet another small cosmetic change to the interface of the io filters. + + Revision 1.18 2007/10/12 10:09:29 corsini + signed/unsigned warning removed + + Revision 1.17 2007/10/12 10:06:58 corsini + fix some warnings + + Revision 1.16 2007/10/08 08:55:44 cignoni + Added automatic exporting of ply and aln from the dialog + + Revision 1.15 2007/04/16 09:25:29 cignoni + ** big change ** + Added Layers managemnt. + Interfaces are changing again... + + Revision 1.14 2007/03/20 16:23:08 cignoni + Big small change in accessing mesh interface. First step toward layers + + Revision 1.13 2007/03/20 15:52:46 cignoni + Patched issue related to path with non ascii chars + + Revision 1.12 2007/02/26 11:41:07 corsini + add more control to depth filter through interface paramters + + Revision 1.11 2007/01/24 08:33:15 cignoni + Still experiments in filtering depth jumps + + Revision 1.10 2007/01/23 14:31:16 corsini + add improved depth filtering to remove artifacts + + Revision 1.9 2007/01/23 11:38:55 cignoni + Added depth jump control in laplacian smoothing of featureless areas of depthmap + + Revision 1.8 2007/01/23 10:50:44 cignoni + Better comments and variable names + + Revision 1.7 2007/01/23 09:21:28 corsini + add mean+erosion filter + + Revision 1.6 2007/01/11 11:48:04 cignoni + Reordered include + + Revision 1.5 2006/12/06 21:25:00 cignoni + small optimization and logging for profiling + + Revision 1.4 2006/11/30 11:40:33 cignoni + Updated the calls to the hole filling functions to the new interface + + Revision 1.3 2006/11/29 00:59:16 cignoni + Cleaned plugins interface; changed useless help class into a plain string + + Revision 1.2 2006/11/08 15:49:42 cignoni + Added quality to the loaded masks + + Revision 1.1 2006/11/07 18:14:21 cignoni + Moved from the epoch svn repository + + Revision 1.1 2006/01/20 13:03:27 cignoni + *** empty log message *** + +*****************************************************************************/ +#include +#include +#include +#include +#include + + + +#include +#include + +#include "epoch_io.h" +#include "epoch_reconstruction.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../cleanfilter/remove_small_cc.h" +#include + +FILE *logFP=0; +using namespace std; +using namespace vcg; + +void EpochModel::depthFilter(FloatImage &depthImgf, FloatImage &countImgf, float depthJumpThr, + bool dilation, int dilationNumPasses, int dilationWinsize, + bool erosion, int erosionNumPasses, int erosionWinsize) +{ + FloatImage depth; + FloatImage depth2; + int w = depthImgf.w; + int h = depthImgf.h; + + depth=depthImgf; + + if (dilation) + { + for (int k = 0; k < dilationNumPasses; k++) + { + depth.Dilate(depth2, dilationWinsize / 2); + depth=depth2; + } + } + + if (erosion) + { + for (int k = 0; k < erosionNumPasses; k++) + { + depth.Erode(depth2, erosionWinsize / 2); + depth=depth2; + } + } + + Histogramf HH; + HH.Clear(); + HH.SetRange(0,depthImgf.MaxVal()-depthImgf.MinVal(),10000); + for(int i=1; i < static_cast(depthImgf.v.size()); ++i) + HH.Add(fabs(depthImgf.v[i]-depth.v[i-1])); + + if(logFP) fprintf(logFP,"**** Depth histogram 2 Min %f Max %f Avg %f Percentiles ((10)%f (25)%f (50)%f (75)%f (90)%f)\n",HH.MinV(),HH.MaxV(),HH.Avg(), + HH.Percentile(.1),HH.Percentile(.25),HH.Percentile(.5),HH.Percentile(.75),HH.Percentile(.9)); + + int deletedCnt=0; + + depthJumpThr = static_cast(HH.Percentile(0.8)); + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) + { + if ((depthImgf.Val(x, y) - depth.Val(x, y)) / depthImgf.Val(x, y) > 0.6) + { + countImgf.Val(x, y) = 0.0f; + ++deletedCnt; + } + } + + countImgf.convertToQImage().save("tmp_filteredcount.jpg","jpg"); + + if(logFP) fprintf(logFP,"**** depthFilter: deleted %i on %i\n",deletedCnt,w*h); + +} + +float EpochModel::ComputeDepthJumpThr(FloatImage &depthImgf, float percentile) +{ + Histogramf HH; + HH.Clear(); + HH.SetRange(0,depthImgf.MaxVal()-depthImgf.MinVal(),10000); + for(unsigned int i=1; i < static_cast(depthImgf.v.size()); ++i) + HH.Add(fabs(depthImgf.v[i]-depthImgf.v[i-1])); + + if(logFP) fprintf(logFP,"**** Depth histogram Min %f Max %f Avg %f Percentiles ((10)%f (25)%f (50)%f (75)%f (90)%f)\n",HH.MinV(),HH.MaxV(),HH.Avg(), + HH.Percentile(.1),HH.Percentile(.25),HH.Percentile(.5),HH.Percentile(.75),HH.Percentile(.9)); + + return HH.Percentile(percentile); +} + + + +/// Apply the hand drawn mask image +bool EpochModel::CombineHandMadeMaskAndCount(CharImage &CountImg, QString maskName ) +{ + QImage maskImg(maskName); + qDebug("Trying to read maskname %s",qPrintable(maskName)); + if(maskImg.isNull()) + return false; + + if( (maskImg.width()!= CountImg.w) || (maskImg.height()!= CountImg.h) ) + { + qDebug("Warning mask and images does not match! %i %i vs %i %i",maskImg.width(),CountImg.w,maskImg.height(),CountImg.h); + return false; + } + + for(int j=0;j128) + CountImg.Val(i,j)=0; + + return true; +} + + +void EpochModel::SmartSubSample(int factor, FloatImage &fli, CharImage &chi, FloatImage &subD, FloatImage &subQ, int minCount) +{ + assert(fli.w==chi.w && fli.h==chi.h); + int w=fli.w/factor; + int h=fli.h/factor; + subQ.resize(w,h); + subD.resize(w,h); + + for(int i=0;i0) + { + maxcount+= q; + bestVal +=q*fli.Val(i*factor+ki,j*factor+kj); + cnt++; + } + } + if(cnt>0) + { + subD.Val(i,j)=float(bestVal)/maxcount; + subQ.Val(i,j)=minCount-1 + float(maxcount)/cnt ; + } + else + { + subD.Val(i,j)=0; + subQ.Val(i,j)=0; + } + } +} + +/* +This filter average apply a laplacian smoothing over a depth map averaging the samples with a weighting scheme that follows the Counting masks. +The result of the laplacian is applied only on sample with low quality. +*/ + +void EpochModel::Laplacian2(FloatImage &depthImg, FloatImage &countImg, int minCount, CharImage &featureMask, float depthThr) +{ + FloatImage Sum; + int w=depthImg.w,h=depthImg.h; + Sum.resize(w,h); + + for(int y=1;y0 && fabs(depthImg.Val(x+i,y+j)-curDepth) < depthThr) { + Sum.Val(x,y)+=q*depthImg.Val(x+i,y+j); + cnt+=q; + } + } + if(cnt>0) { + Sum.Val(x,y)/=cnt; + } + else Sum.Val(x,y)=depthImg.Val(x,y); + } + + for(int y=1;y(m,depthSubf.w,depthSubf.h,depthImgf.w,depthImgf.h,&*depthSubf.v.begin()); + + int ttt4=clock(); + if(logFP) fprintf(logFP,"**** Buildmesh: trimesh building %i\n",ttt4-ttt3); + + + // The depth is filtered and the minimum count mask is update accordingly. + // To be more specific the border of the depth map are identified by erosion + // and the relative vertex removed (by setting mincount equal to 0). + float depthThr2 = ComputeDepthJumpThr(depthSubf,0.95f); + depthFilter(depthSubf, countSubf, depthThr2, + dilation, dilationPasses, dilationSize, + erosion, erosionPasses, erosionSize); + + int vn = m.vn; + for(int i=0;iIsD() ||(*fi).V(1)->IsD() ||(*fi).V(2)->IsD() ) + { + (*fi).SetD(); + --m.fn; + } + else + { + Point3f n=vcg::Normal(*fi); + n.Normalize(); + Point3f dir=CameraPos-vcg::Barycenter(*fi); + dir.Normalize(); + if(dir.dot(n) < minAngleCos) + { + (*fi).SetD(); + --m.fn; + } + } + } + + int ttt6=clock(); + if(logFP) fprintf(logFP,"**** Buildmesh: Deleting skewed %i\n",ttt6-ttt5); + +// Matrix44d Rot; +// Rot.SetRotate(M_PI,Point3d(1,0,0)); +// vcg::tri::UpdatePosition::Matrix(m, Rot); + + Matrix44f scaleMat; + scaleMat.SetScale(scalingFactor,scalingFactor,scalingFactor); + vcg::tri::UpdatePosition::Matrix(m, scaleMat); + + return true; +} +void EpochModel::AddCameraIcon(CMeshO &m) +{ + tri::Allocator::AddVertices(m,3); + m.vert[m.vert.size()-3].P()=Point3f::Construct(cam.t+Point3d(0,0,0)); + m.vert[m.vert.size()-3].C()=Color4b::Green; + m.vert[m.vert.size()-2].P()=Point3f::Construct(cam.t+Point3d(0,1,0)); + m.vert[m.vert.size()-2].C()=Color4b::Green; + m.vert[m.vert.size()-1].P()=Point3f::Construct(cam.t+Point3d(1,0,0)); + m.vert[m.vert.size()-1].C()=Color4b::Green; + + tri::Allocator::AddFaces(m,1); + m.face[m.face.size()-1].V(0)= &m.vert[m.vert.size()-3]; + m.face[m.face.size()-1].V(1)= &m.vert[m.vert.size()-2]; + m.face[m.face.size()-1].V(2)= &m.vert[m.vert.size()-1]; + } + + + + + + +bool EpochModel::Init(QDomNode &node) +{ + if(!node.hasAttributes()) return false; + QDomNamedNodeMap attr= node.attributes(); + QString indexString = (attr.namedItem("index")).nodeValue() ; + qDebug("reading Model with index %i ",indexString.toInt()); + for(QDomNode n = node.firstChild(); !n.isNull(); n = n.nextSibling()) + { + if(n.nodeName() == QString("camera")) cameraName = n.attributes().namedItem("filename").nodeValue(); + if(n.nodeName() == QString("texture")) textureName= n.attributes().namedItem("filename").nodeValue(); + if(n.nodeName() == QString("depth")) depthName = n.attributes().namedItem("filename").nodeValue(); + if(n.nodeName() == QString("count")) countName = n.attributes().namedItem("filename").nodeValue(); + } + + QString tmpName=textureName.left(textureName.length()-4); + maskName = tmpName.append(".mask.png"); + return true; +} + +QString EpochModel::ThumbName(QString &_imageName) +{ + QString tmpName=_imageName.left(_imageName.length()-4); + return tmpName.append(".thumb.jpg"); +} + +EpochIO::EpochIO() + { + epochDialog = new v3dImportDialog(); + epochDialog->hide(); + } + + EpochIO::~EpochIO() + { + delete epochDialog; + } + +bool EpochIO::open(const QString &formatName, const QString &fileName, MeshModel &m, int& mask,const FilterParameterSet & /*par*/, CallBackPos *cb, QWidget *parent) +{ + EpochReconstruction er; + + mask = vcg::tri::io::Mask::IOM_VERTCOLOR | vcg::tri::io::Mask::IOM_VERTQUALITY; +// just to be sure... + + if (fileName.isEmpty()) return false; + // initializing progress bar status + if (cb != NULL) (*cb)(0, "Loading..."); + + // this change of dir is needed for subsequent texture/material loading + QString FileNameDir = fileName.left(fileName.lastIndexOf("/")); + QDir::setCurrent(FileNameDir); + + QString errorMsgFormat = "Error encountered while loading file %1:\n%2"; + string stdfilename = QFile::encodeName(fileName).constData (); + //string filename = fileName.toUtf8().data(); + + QDomDocument doc; + + if(formatName.toUpper() == tr("V3D") && fileName.endsWith(".v3d")) + { + QFile file(fileName); + if (file.open(QIODevice::ReadOnly) && doc.setContent(&file)) + { + file.close(); + QDomElement root = doc.documentElement(); + if (root.nodeName() == tr("reconstruction")) + { + QDomNode nhead = root.firstChildElement("head"); + for(QDomNode n = nhead.firstChildElement("meta"); !n.isNull(); n = n.nextSiblingElement("meta")) + { + if(!n.hasAttributes()) return false; + QDomNamedNodeMap attr= n.attributes(); + if(attr.contains("name")) er.name = (attr.namedItem("name")).nodeValue() ; + if(attr.contains("author")) er.author = (attr.namedItem("author")).nodeValue() ; + if(attr.contains("created")) er.created = (attr.namedItem("created")).nodeValue() ; + } + for(QDomNode n = root.firstChildElement("model"); !n.isNull(); n = n.nextSiblingElement("model")) + { + EpochModel em; + em.Init(n); + er.modelList.push_back(em); + } + } + } + } + + epochDialog->setEpochReconstruction( &er, cb); + do + { + epochDialog->exportToPLY=false; + + //Here we invoke the modal dialog and wait for its termination + int continueValue = epochDialog->exec(); + + // The user has pressed the ok button: now start the real processing: + + if(epochDialog->exportToPLY == true) qDebug("Starting the ply exporting process"); + + int t0=clock(); + logFP=fopen("epoch.log","w"); + + int subSampleVal = epochDialog->subsampleSpinBox->value(); + int minCountVal= epochDialog->minCountSpinBox->value(); + float maxCCDiagVal= epochDialog->maxCCDiagSpinBox->value(); + int mergeResolution=epochDialog->mergeResolutionSpinBox->value(); + int smoothSteps=epochDialog->smoothSpinBox->value(); + bool closeHole = epochDialog->holeCheckBox->isChecked(); + int maxHoleSize = epochDialog->holeSpinBox->value(); + if (continueValue == QDialog::Rejected) + { + QMessageBox::warning(parent, "Open V3d format","Aborted"); + return false; + } + CMeshO mm; + QTableWidget *qtw=epochDialog->imageTableWidget; + float MinAngleCos=cos(vcg::math::ToRad(epochDialog->qualitySpinBox->value())); + bool clustering=epochDialog->fastMergeCheckBox->isChecked(); + bool removeSmallCC=epochDialog->removeSmallCCCheckBox->isChecked(); + vcg::tri::Clustering > Grid; + + int selectedNum=0,selectedCount=0; + int i; + for(i=0;irowCount();++i) if(qtw->isItemSelected(qtw->item(i,0))) ++selectedNum; + if(selectedNum==0) + { + QMessageBox::warning(parent, "Open V3d format","No range map selected. Nothing loaded"); + return false; + } + + bool dilationFlag = epochDialog->dilationCheckBox->isChecked(); + int dilationN = epochDialog->dilationNumPassSpinBox->value(); + int dilationSz = epochDialog->dilationSizeSlider->value() * 2 + 1; + bool erosionFlag = epochDialog->erosionCheckBox->isChecked(); + int erosionN = epochDialog->erosionNumPassSpinBox->value(); + int erosionSz = epochDialog->erosionSizeSlider->value() * 2 + 1; + float scalingFactor = epochDialog->scaleLineEdit->text().toFloat(); + std::vector savedMeshVector; + + bool firstTime=true; + QList::iterator li; + for(li=er.modelList.begin(), i=0;li!=er.modelList.end();++li,++i) + { + if(qtw->isItemSelected(qtw->item(i,0))) + { + ++selectedCount; + mm.Clear(); + int tt0=clock(); + (*li).BuildMesh(mm,subSampleVal,minCountVal,MinAngleCos,smoothSteps, + dilationFlag, dilationN, dilationSz, erosionFlag, erosionN, erosionSz,scalingFactor); + int tt1=clock(); + if(logFP) fprintf(logFP,"** Mesh %i : Build in %i\n",selectedCount,tt1-tt0); + + if(epochDialog->exportToPLY) + { + QString plyFilename =(*li).textureName.left((*li).textureName.length()-4); + plyFilename.append(".x.ply"); + savedMeshVector.push_back(qPrintable(plyFilename)); + int mask= tri::io::Mask::IOM_VERTCOORD + tri::io::Mask::IOM_VERTCOLOR + tri::io::Mask::IOM_VERTQUALITY; + int result = tri::io::ExporterPLY::Save(mm,qPrintable(plyFilename),mask); + } + else + { + if(clustering) + { + if (firstTime) + { + //Grid.Init(mm.bbox,100000); + vcg::tri::UpdateBounding::Box(mm); + //Grid.Init(mm.bbox,1000.0*pow(10.0,mergeResolution),mm.bbox.Diag()/1000.0f); + Grid.Init(mm.bbox,100000.0*pow(10.0,mergeResolution)); + firstTime=false; + } + Grid.Add(mm); + } + else + tri::Append::Mesh(m.cm,mm); // append mesh mr to ml + } + int tt2=clock(); + if(logFP) fprintf(logFP,"** Mesh %i : Append in %i\n",selectedCount,tt2-tt1); + + } + if (cb)(*cb)(selectedCount*90/selectedNum, "Building meshes"); + } + + if (cb != NULL) (*cb)(90, "Final Processing: clustering"); + if(clustering) + { + Grid.Extract(m.cm); + } + + if(epochDialog->exportToPLY) + { + QString ALNfilename = fileName.left(fileName.length()-4).append(".aln"); + ALNParser::SaveALN(qPrintable(ALNfilename), savedMeshVector); + } + int t1=clock(); + if(logFP) fprintf(logFP,"Extracted %i meshes in %i\n",selectedCount,t1-t0); + + if (cb != NULL) (*cb)(95, "Final Processing: Removing Small Connected Components"); + if(removeSmallCC) + { + vcg::tri::UpdateBounding::Box(m.cm); // updates bounding box + m.updateDataMask(MeshModel::MM_FACEFACETOPO | MeshModel::MM_FACEFLAGBORDER | MeshModel::MM_FACEMARK); + RemoveSmallConnectedComponentsDiameter(m.cm,m.cm.bbox.Diag()*maxCCDiagVal/100.0); + } + + int t2=clock(); + if(logFP) fprintf(logFP,"Topology and removed CC in %i\n",t2-t1); + + vcg::tri::UpdateBounding::Box(m.cm); // updates bounding box + + if (cb != NULL) (*cb)(97, "Final Processing: Closing Holes"); + if(closeHole) + { + m.updateDataMask(MeshModel::MM_FACEFACETOPO | MeshModel::MM_FACEFLAGBORDER | MeshModel::MM_FACEMARK); + tri::UpdateNormals::PerVertexNormalizedPerFace(m.cm); + vcg::tri::Hole::EarCuttingFill >(m.cm,maxHoleSize,false); + } + + if (cb != NULL) (*cb)(100, "Done"); + // vcg::tri::UpdateNormals::PerVertex(m.cm); // updates normals + + + int t3=clock(); + if(logFP) fprintf(logFP,"---------- Total Processing Time%i\n\n\n",t3-t0); + if(logFP) fclose(logFP); + logFP=0; + } while(epochDialog->exportToPLY); + + return true; +} + + +bool EpochIO::save(const QString &/*formatName*/,const QString &/*fileName*/, MeshModel &/*m*/, const int /*mask*/, const FilterParameterSet &, vcg::CallBackPos * /*cb*/, QWidget *parent) +{ + QMessageBox::warning(parent, "Unknown type", "file's extension not supported!!!"); + return false; +} + +QList EpochIO::importFormats() const +{ + QList formatList; + formatList << Format("Epoch Reconstructed mesh","V3D"); + return formatList; +}; + +QIcon *EpochModel::getIcon() +{ + QString iconName(textureName); + iconName+=QString(".xbm"); + QIcon *ico=new QIcon(); + + + return ico; +} + +Q_EXPORT_PLUGIN(EpochIO) + diff --git a/src/meshlabplugins/io_epoch/epoch_io.h b/src/meshlabplugins/io_epoch/epoch_io.h new file mode 100644 index 000000000..c50b80d6a --- /dev/null +++ b/src/meshlabplugins/io_epoch/epoch_io.h @@ -0,0 +1,74 @@ +/**************************************************************************** +* MeshLab o o * +* A versatile mesh processing toolbox o o * +* _ O _ * +* Copyright(C) 2005 \/)\/ * +* Visual Computing Lab /\/| * +* ISTI - Italian National Research Council | * +* \ * +* All rights reserved. * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License (http://www.gnu.org/licenses/gpl.txt) * +* for more details. * +* * +****************************************************************************/ +/**************************************************************************** + History + + $Log$ + Revision 1.4 2007/11/26 07:35:26 cignoni + Yet another small cosmetic change to the interface of the io filters. + + Revision 1.3 2007/11/25 09:48:39 cignoni + Changed the interface of the io filters. Now also a default bit set for the capabilities has to specified + + Revision 1.2 2006/11/29 00:59:16 cignoni + Cleaned plugins interface; changed useless help class into a plain string + + Revision 1.1 2006/11/07 18:14:21 cignoni + Moved from the epoch svn repository + + Revision 1.1 2006/01/20 13:03:27 cignoni + *** empty log message *** + + *****************************************************************************/ +#ifndef EXTRAIOPLUGINV3D_H +#define EXTRAIOPLUGINV3D_H + +#include +#include +#include + +#include +#include +#include "v3dImportDialog.h" + +class EpochIO : public QObject, public MeshIOInterface +{ + Q_OBJECT + Q_INTERFACES(MeshIOInterface) + +public: + QList importFormats() const; + QList exportFormats() const {return QList();}; + + EpochIO(); + ~EpochIO(); + v3dImportDialog *epochDialog; + QString lastFileName; + void GetExportMaskCapability(QString &, int &, int &) const {assert(0); return ;} + + bool open(const QString &formatName, const QString &fileName, MeshModel &m, int& mask,const FilterParameterSet & par, vcg::CallBackPos *cb=0, QWidget *parent=0); + bool save(const QString &formatName, const QString &fileName, MeshModel &m, const int mask, const FilterParameterSet &, vcg::CallBackPos *cb=0, QWidget *parent= 0); +}; + + +#endif diff --git a/src/meshlabplugins/io_epoch/epoch_reconstruction.h b/src/meshlabplugins/io_epoch/epoch_reconstruction.h new file mode 100644 index 000000000..a571df16e --- /dev/null +++ b/src/meshlabplugins/io_epoch/epoch_reconstruction.h @@ -0,0 +1,50 @@ +#ifndef _EPOCH_RECONSTRUCTION_H +#define _EPOCH_RECONSTRUCTION_H + +#include +#include +#include + +#include + +#include "radial_distortion.h" +#include "epoch_camera.h" +#include "scalar_image.h" + + +class EpochModel +{ +public: + int index; + QString cameraName; + QString maskName; + QString depthName; + QString textureName; + QString countName; + vcg::EpochCamera cam; + bool Init(QDomNode &node); + static QString ThumbName(QString &imageName); + + bool BuildMesh(CMeshO &m, int subsampleFactor, int minCount, float minAngleCos, int smoothSteps, + bool dilation, int dilationPasses, int dilationSize, bool erosion, int erosionPasses, int erosionSize,float scalingFactor); + void SmartSubSample(int subsampleFactor, FloatImage &fli, CharImage &chi, FloatImage &subD,FloatImage &subQ, int minCount); + void AddCameraIcon(CMeshO &m); + bool CombineHandMadeMaskAndCount(CharImage &qualityImg, QString maskName ); + void GenerateCountImage(); + void GenerateGradientSmoothingMask(int subsampleFactor, QImage &OriginalTexture, CharImage &mask); + void Laplacian2(FloatImage &depth, FloatImage &Q, int minCount, CharImage &mask, float depthThr); + float ComputeDepthJumpThr(FloatImage &depthImgf, float percentile); + void depthFilter(FloatImage &depthImgf, FloatImage &countImgf, float depthJumpThr, + bool dilation, int dilationNumPasses, int dilationWinsize, bool erosion, int erosionNumPasses, int erosionWinsize); + + QIcon *getIcon(); +}; + +class EpochReconstruction +{ + public: + QString name, author, created; + QList modelList; +}; + +#endif \ No newline at end of file diff --git a/src/meshlabplugins/io_epoch/fillImage.cpp b/src/meshlabplugins/io_epoch/fillImage.cpp new file mode 100644 index 000000000..93b5cd4c6 --- /dev/null +++ b/src/meshlabplugins/io_epoch/fillImage.cpp @@ -0,0 +1,104 @@ +#include "fillImage.h" +#include +#include + +namespace ui +{ + + fillImage::fillImage() + { + } + + fillImage::~fillImage() + { + } + + void fillImage::Compute(const QImage& input, int x, int y, int threshold_gradient, int threshold_fixed, QImage& output) + { + threshold_gradient_ = threshold_gradient; + threshold_fixed_ = threshold_fixed; + input_ = input; + W = input.width(); + H = input.height(); + xo = x; + yo = y; + + output = QImage(W, H, QImage::Format_Mono); + computed_ = QImage(W, H, QImage::Format_Mono); + output.fill(0); + computed_.fill(0); + + ComputeGradient(input, gradient_); + + pixels_to_do_.push_back(std::make_pair(x,y)); + while(!pixels_to_do_.empty()) + { + DealWithPixel(pixels_to_do_.front(),output); + pixels_to_do_.pop_front(); + } + + } + + bool fillImage::ShouldWeCompute(int x, int y) + { + if (input_.isGrayscale()) + return (gradient_.get(x,y) < threshold_gradient_ && computed_.pixelIndex(x,y) == 0 && std::abs(qGray(input_.pixel(x,y)) - qGray(input_.pixel(xo,yo))) < threshold_fixed_); + else + return (gradient_.get(x,y) < threshold_gradient_ && computed_.pixelIndex(x,y) == 0 && std::abs(qRed(input_.pixel(x,y)) - qRed(input_.pixel(xo,yo))) < threshold_fixed_ && std::abs(qGreen(input_.pixel(x,y)) - qGreen(input_.pixel(xo,yo))) < threshold_fixed_ && std::abs(qBlue(input_.pixel(x,y)) - qBlue(input_.pixel(xo,yo))) < threshold_fixed_); + } + + void fillImage::DealWithPixel(const std::pair& xy, QImage& output) + { + int x = xy.first; + int y = xy.second; + if (computed_.pixelIndex(x,y) == 1) + return; + output.setPixel(x,y,1); + computed_.setPixel(x,y,1); + + if (x>0 && ShouldWeCompute(x-1,y)) + pixels_to_do_.push_back(std::make_pair(x-1,y)); + if (x0 && ShouldWeCompute(x, y-1)) + pixels_to_do_.push_back(std::make_pair(x,y-1)); + if (y::max(); + float max = -std::numeric_limits::max(); + + for (size_t i=1; i max) + max = dx; + if (dx < min) + min = dx; + } + output = myGSImage(W, H); + + float range = 255./(max-min); + for (size_t i=0; i +#include +#include + +namespace ui +{ + struct myGSImage + { + unsigned char* data; + size_t w, h; + + myGSImage() + { + data = 0; + } + + myGSImage(const myGSImage& image) + { + w = image.w; + h = image.h; + if (data) + delete [] data; + data = new unsigned char[w*h]; + memcpy(data, image.data, w*h); + } + + myGSImage & operator = (const myGSImage& image) + { + w = image.w; + h = image.h; + if (data) + delete [] data; + data = new unsigned char[w*h]; + memcpy(data, image.data, w*h); + return *this; + } + + myGSImage(size_t width, size_t height): w(width), h(height) + { + data = new unsigned char[width*height]; + }; + + ~myGSImage() + { + if (data) + delete [] data; + } + + unsigned char get(size_t i, size_t j) const + { + return data[j*w + i]; + } + + void put(size_t i, size_t j, unsigned char a) + { + data[j*w + i] = a; + } + + void fill(unsigned char a) + { + for (size_t i=0; i&, QImage& output); + QImage input_, computed_; + myGSImage gradient_; + int threshold_gradient_, threshold_fixed_; + int W,H; + int xo, yo; + std::deque > pixels_to_do_; + }; +} + +#endif diff --git a/src/meshlabplugins/io_epoch/maskImageWidget.cpp b/src/meshlabplugins/io_epoch/maskImageWidget.cpp new file mode 100644 index 000000000..9cdb0f2a9 --- /dev/null +++ b/src/meshlabplugins/io_epoch/maskImageWidget.cpp @@ -0,0 +1,310 @@ +#include "maskImageWidget.h" +#include "maskRenderWidget.h" +#include "fillImage.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef WIN32 +#undef min +#undef max +#endif + + +namespace ui +{ + + struct maskImageWidget::Impl + { + enum DrawMode { Pen, Eraser } mode_; + + maskRenderWidget *render_area_; + int threshold_gradient_, threshold_fixed_; + int realwidth_, realheight_; + Impl(); + }; + + + maskImageWidget::Impl::Impl() + { + mode_ = Pen; + threshold_gradient_ = 100; + threshold_fixed_ = 30; + } + + + maskImageWidget::maskImageWidget(const QImage& image, QWidget *parent) : QDialog(parent), pimpl_(new Impl) + { + init(image); + } + + + maskImageWidget::~maskImageWidget() throw() + { + delete pimpl_; + } + + QImage maskImageWidget::getMask() const + { + return pimpl_->render_area_->getMask(pimpl_->realwidth_, pimpl_->realheight_); + } + + void maskImageWidget::loadMask(const QString& filename) + { + pimpl_->render_area_->load(filename); + } + + void maskImageWidget::init(const QImage& image) + { + setWindowTitle(tr("Mask Editor")); + + QPixmap load("coral_open32x32.png"); + QPixmap save("coral_save32x32.png"); + QPixmap undo("coral_undo32x32.png"); + QPixmap redo("coral_redo32x32.png"); + QPixmap pen("coral_pencil32x32.png"); + QPixmap eraser("coral_eraser32x32.png"); + + QAction *canvasloadmask = new QAction(this); + canvasloadmask->setIcon(load); + canvasloadmask->setText(tr("&Load Mask")); + QAction *canvassavemask = new QAction(this); + canvassavemask->setIcon(QIcon(save)); + canvassavemask->setText(tr("&Save Mask")); + QAction *canvasundo = new QAction(this); + canvasundo->setIcon(QIcon(undo)); + canvasundo->setText(tr("&Undo")); + canvasundo->setShortcut(QKeySequence("Ctrl+Z")); + QAction *canvasredo = new QAction(this); + canvasredo->setIcon(QIcon(redo)); + canvasredo->setText(tr("&Redo")); + canvasredo->setShortcut(QKeySequence("Ctrl+Shift+Z")); + QAction *canvasclear = new QAction(tr("&Clear"), this); + canvasclear->setShortcut(QKeySequence("Ctrl+C")); + + QAction *canvaspen = new QAction(this); + canvaspen->setIcon(QIcon(pen)); + canvaspen->setText(tr("&Pen")); + QAction *canvaseraser = new QAction(this); + canvaseraser->setIcon(QIcon(eraser)); + canvaseraser->setText(tr("&Eraser")); + + QActionGroup *actions(new QActionGroup(this)); + actions->addAction(canvaspen); + actions->addAction(canvaseraser); + canvaspen->setCheckable(true); + canvaseraser->setCheckable(true); + canvaspen->setChecked(true); + actions->setExclusive(true); + + QAction *canvasOK = new QAction(this); + canvasOK->setText("OK"); + QAction *canvasCancel = new QAction(this); + canvasCancel->setText("Cancel"); + + QBoxLayout *layout(new QVBoxLayout(this)); + + // We don't want a real-size image. We will downscale it! + QImage image_to_use = image; + pimpl_->realwidth_ = image.width(); + pimpl_->realheight_ = image.height(); + qDebug("maskImageWidget::Init real wxh %i x%i",pimpl_->realwidth_,pimpl_->realheight_); + QDesktopWidget *desktop(QApplication::desktop()); + if (image.width() > (desktop->width() * .8) || + image.height() > (desktop->height() * .8)) + { + int width(desktop->width()), height(desktop->height()); + image_to_use = image.scaled((int)std::floor(width * .75), + (int)std::floor(height * .75), Qt::KeepAspectRatio); + } + pimpl_->render_area_ = new maskRenderWidget(image_to_use, this); + + QToolBar *canvas_toolbar(new QToolBar(this)); + canvas_toolbar->addSeparator(); + canvas_toolbar->addAction(canvasloadmask); + canvas_toolbar->addAction(canvassavemask); + canvas_toolbar->addSeparator(); + + canvas_toolbar->addAction(canvasundo); + canvas_toolbar->addAction(canvasredo); + canvas_toolbar->addSeparator(); + + QSpinBox *pen_width(new QSpinBox(canvas_toolbar)); + pen_width->setToolTip(tr("Pen Width")); + pen_width->setRange(0, 80); + pen_width->setSingleStep(2); + pen_width->setValue(16); + connect(pen_width, SIGNAL(valueChanged(int)), SLOT(setCanvasPenWidth(int))); + canvas_toolbar->addWidget(pen_width); + canvas_toolbar->addAction(canvaspen); + canvas_toolbar->addAction(canvaseraser); + canvas_toolbar->addSeparator(); + + QSpinBox *gradient(new QSpinBox(canvas_toolbar)); + gradient->setToolTip("Gradient Threshold"); + gradient->setRange(0, 255); + gradient->setValue(pimpl_->threshold_gradient_); + connect(gradient, SIGNAL(valueChanged(int)), SLOT(setGradientThreshold(int))); + + QSpinBox *fixed(new QSpinBox(canvas_toolbar)); + fixed->setToolTip("Fixed Threshold"); + fixed->setRange(0, 255); + fixed->setValue(pimpl_->threshold_fixed_); + connect(fixed, SIGNAL(valueChanged(int)), SLOT(setFixedThreshold(int))); + + canvas_toolbar->addWidget(gradient); + canvas_toolbar->addWidget(fixed); + canvas_toolbar->addSeparator(); + + canvas_toolbar->addAction(canvasOK); + canvas_toolbar->addAction(canvasCancel); + + layout->addWidget(canvas_toolbar); + layout->addWidget(pimpl_->render_area_); + layout->setSizeConstraint(QLayout::SetFixedSize); + + connect(canvasloadmask, SIGNAL(activated()), SLOT(loadMask())); + connect(canvassavemask, SIGNAL(activated()), SLOT(saveMask())); + connect(canvasundo, SIGNAL(activated()), pimpl_->render_area_, SLOT(undo())); + connect(canvasredo, SIGNAL(activated()), pimpl_->render_area_, SLOT(redo())); + connect(canvasclear, SIGNAL(activated()), pimpl_->render_area_, SLOT(clear())); + connect(canvaspen, SIGNAL(activated()), SLOT(setCanvasPen())); + connect(canvaseraser, SIGNAL(activated()), SLOT(setCanvasEraser())); + + connect(pimpl_->render_area_, SIGNAL(pointSelected(const QPoint &)), SLOT(automaticMask(const QPoint &))); + + connect(canvasOK, SIGNAL(activated()), SLOT(accept())); + connect(canvasCancel, SIGNAL(activated()), SLOT(reject())); + } + + void maskImageWidget::setCanvasPenWidth(int width) + { + QPen pen(pimpl_->render_area_->pen()); + pen.setWidth(width); + pimpl_->render_area_->setPen(pen); + } + + + void maskImageWidget::setCanvasPen() + { + QPen pen(pimpl_->render_area_->pen()); + pen.setColor(QColor(Qt::black)); + pen.setJoinStyle(Qt::RoundJoin); + pimpl_->render_area_->setPen(pen); + } + + + void maskImageWidget::setCanvasEraser() + { + QPen pen(pimpl_->render_area_->pen()); + pen.setColor(QColor(Qt::transparent)); + pen.setJoinStyle(Qt::RoundJoin); + pimpl_->render_area_->setPen(pen); + } + + + void maskImageWidget::setGradientThreshold(int threshold_gradient) + { + pimpl_->threshold_gradient_ = threshold_gradient; + } + + + void maskImageWidget::setFixedThreshold(int threshold_fixed) + { + pimpl_->threshold_fixed_ = threshold_fixed; + } + + void maskImageWidget::loadMask() + { + try + { + QString filename(QFileDialog::getOpenFileName(this, QString("Open mask file"), QString(), QString("*.png"))); + if (QString::null != filename) + pimpl_->render_area_->load(filename); + } + catch (std::exception &e) + { + QMessageBox::warning(this, tr("Problem"), e.what()); + } + } + + namespace + { + bool check_extension(QString &filename, const QString &ext) + { + bool ret(false); + if (ext != filename.section('.', -1)) + { + int index(filename.lastIndexOf('.')); + if (-1 == index) + { + filename += '.'; + index += filename.size(); + } + filename.replace(index + 1, ext.size(), ext); + filename.resize(index + 1 + ext.size()); + ret = true; + } + return ret; + } + }; + + void maskImageWidget::saveMask() + { + try + { + QString filename(QFileDialog::getSaveFileName(this, QString("Save mask file"), QString(), QString("*.png"))); + if (QString::null != filename) + { + check_extension(filename, QString("png")); + pimpl_->render_area_->save(filename, pimpl_->realwidth_, pimpl_->realheight_); + } + } + catch (std::exception &e) + { + QMessageBox::warning(this, tr("Epoch 3D Webservice"), e.what()); + } + } + + + void maskImageWidget::automaticMask(const QPoint &p) + { + QImage image = (pimpl_->render_area_->palette().base().texture()).toImage(); + QImage out; + fillImage fi; + fi.Compute(image, p.x(), p.y(), pimpl_->threshold_gradient_, pimpl_->threshold_fixed_, out); + + const size_t width(image.width()), height(image.height()); + QImage temp(pimpl_->render_area_->alphaMask()); + for (size_t i = 0; i < width; ++i) + { + for (size_t j = 0; j < height; ++j) + { + if (out.pixelIndex(i, j) > 0) + temp.setPixel(i, j, QColor(Qt::black).rgba()); + } + } + //temp.save("temp.jpg","jpg"); + pimpl_->render_area_->setAlphaMask(temp); + } +}; diff --git a/src/meshlabplugins/io_epoch/maskImageWidget.h b/src/meshlabplugins/io_epoch/maskImageWidget.h new file mode 100644 index 000000000..242776a73 --- /dev/null +++ b/src/meshlabplugins/io_epoch/maskImageWidget.h @@ -0,0 +1,53 @@ +#ifndef __IO_MASK_IMAGE_WIDGET_INC__ +#define __IO_MASK_IMAGE_WIDGET_INC__ + + +#include + + +namespace ui +{ + /*! \class maskImageWidget + \brief A brief description + \author Maarten Vergauwen + */ + class maskImageWidget : public QDialog + { + Q_OBJECT + + struct Impl; + Impl* pimpl_; + + void init (const QImage &); + + public: + /*! \brief Constructor + \param The image. + */ + explicit maskImageWidget(const QImage &, QWidget *parent = 0); +; + /*! \brief Destructor + */ + virtual ~maskImageWidget() throw(); + + QImage getMask() const; + + public slots: + + void loadMask(const QString& filename); + + private slots: + + void setCanvasPenWidth(int); + void setCanvasPen(); + void setCanvasEraser(); + void setGradientThreshold(int); + void setFixedThreshold(int); + void automaticMask(const QPoint &); + void loadMask(); + void saveMask(); + }; +}; + + +#endif diff --git a/src/meshlabplugins/io_epoch/maskRenderWidget.cpp b/src/meshlabplugins/io_epoch/maskRenderWidget.cpp new file mode 100644 index 000000000..525c2774b --- /dev/null +++ b/src/meshlabplugins/io_epoch/maskRenderWidget.cpp @@ -0,0 +1,362 @@ +#include "maskRenderWidget.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef WIN32 +#undef min +#undef max +#endif + + +namespace ui +{ + namespace priv + { + template + inline void unwind(std::stack &stack) + { + while (!stack.empty()) + stack.pop(); + } + }; + + + struct maskRenderWidget::Impl + { + enum Shape { Nothing, Polyline, Rect, Rubber, Point } shape_; + + QPen pen_; + QPolygon polygon_; + + QPoint start_point_, last_point_, end_point_; + QRect rubber_band_; + + QImage foreground_, band_buffer_; + std::stack undo_, redo_; + + Impl(); + + void paintOnDevice(QPaintDevice *); + }; + + + maskRenderWidget::Impl::Impl() : pen_(Qt::black) + { + shape_ = Nothing; + pen_.setWidth(16); + pen_.setCapStyle(Qt::RoundCap); + } + + + void maskRenderWidget::Impl::paintOnDevice(QPaintDevice *device) + { + assert(device); + QPainter painter(device); + painter.setCompositionMode(QPainter::CompositionMode_Source); + switch (shape_) + { + case Impl::Polyline: + { + painter.setPen(pen_); + painter.drawPolyline(polygon_); + } + break; + case Impl::Point: + { + painter.setPen(pen_); + QPoint p2(end_point_.x() + 1, end_point_.y() + 1); + painter.drawLine(end_point_, p2); + } + break; + case Impl::Rect: + { + QPen pen; + pen.setColor(pen_.color()); + painter.setPen(pen); + const int x(rubber_band_.x()), y(rubber_band_.y()); + const int w(rubber_band_.width()), h(rubber_band_.height()); + for (int i = 0; i < w; ++i) + for (int j = 0; j < h; ++j) + painter.drawPoint(QPoint(x + i, y + j)); + rubber_band_ = QRect(0, 0, 0, 0); + } + break; + case Impl::Rubber: + { + QPen pen(Qt::gray); + pen.setWidth(1); + painter.setPen(pen); + painter.drawRect(rubber_band_); + } + break; + default: + break; + } + } + + + maskRenderWidget::maskRenderWidget(QWidget *parent) : QWidget(parent), pimpl_(new Impl) + { + setAttribute(Qt::WA_StaticContents); + setBackgroundRole(QPalette::Base); + QImage image(640, 480, QImage::Format_ARGB32); + image.fill(Qt::white); + setImage(image); + setFocusPolicy(Qt::StrongFocus); + } + + maskRenderWidget::maskRenderWidget(const QImage& image, QWidget *parent) : QWidget(parent), pimpl_(new Impl) + { + qDebug("MaskRenderWidget started with an image %i x %i",image.width(),image.height()); + setAttribute(Qt::WA_StaticContents); + setBackgroundRole(QPalette::Base); + setImage(image); + setFocusPolicy(Qt::StrongFocus); + } + + maskRenderWidget::~maskRenderWidget() throw() + { + delete pimpl_; + } + + void maskRenderWidget::keyPressEvent(QKeyEvent *e) + { + if (e->key() == Qt::Key_Z && (e->modifiers() & Qt::ControlModifier)) + { + undo(); + } + } + + + void maskRenderWidget::mousePressEvent(QMouseEvent *e) + { + if (e->button() == Qt::LeftButton) + { + if (e->modifiers() & Qt::ShiftModifier) + { + emit pointSelected(e->pos()); + } + else + { + pimpl_->undo_.push(pimpl_->foreground_); + pimpl_->end_point_ = e->pos(); + pimpl_->polygon_ = QPolygon(); + pimpl_->polygon_ << e->pos(); + priv::unwind(pimpl_->redo_); + pimpl_->shape_ = Impl::Point; + update(); + } + } + else if (e->button() == Qt::RightButton) + { + pimpl_->undo_.push(pimpl_->foreground_); + QApplication::setOverrideCursor(QCursor(Qt::CrossCursor)); + pimpl_->start_point_ = e->pos(); + pimpl_->shape_ = Impl::Rubber; + } + } + + + void maskRenderWidget::mouseMoveEvent(QMouseEvent *e) + { + if (Impl::Rubber == pimpl_->shape_) + { + pimpl_->band_buffer_ = pimpl_->foreground_; + int x(std::min(e->pos().x(), pimpl_->start_point_.x())); + int y(std::min(e->pos().y(), pimpl_->start_point_.y())); + int w(std::abs((float)e->pos().x() - pimpl_->start_point_.x())); + int h(std::abs((float)e->pos().y() - pimpl_->start_point_.y())); + + pimpl_->rubber_band_ = QRect(x, y, w, h); + update(); + } + else if (Impl::Point == pimpl_->shape_) + { + pimpl_->shape_ = Impl::Polyline; + } + else if (Impl::Polyline == pimpl_->shape_) + { + pimpl_->last_point_ = pimpl_->end_point_; + pimpl_->end_point_ = e->pos(); + pimpl_->polygon_ << e->pos(); + update(); + } + } + + + void maskRenderWidget::mouseReleaseEvent(QMouseEvent *e) + { + if (Impl::Rubber == pimpl_->shape_) + { + QApplication::restoreOverrideCursor(); + pimpl_->shape_ = Impl::Rect; + update(); + return; + } + else if (Impl::Polyline == pimpl_->shape_) + { + pimpl_->last_point_ = pimpl_->end_point_; + pimpl_->end_point_ = e->pos(); + update(); + } + pimpl_->shape_ = Impl::Nothing; + } + + + void maskRenderWidget::paintEvent(QPaintEvent *e) + { + QImage * device = &pimpl_->foreground_; + + if (Impl::Rubber == pimpl_->shape_) + device = &pimpl_->band_buffer_; + + pimpl_->paintOnDevice(device); + + QPainter painter(this); + QVector rects(e->region().rects()); + for (int i = 0; i < rects.count(); ++i) + { + QRect r = rects[i]; + painter.drawImage(r, *device, r); + } + } + + + QSize maskRenderWidget::sizeHint() const + { + return minimumSizeHint(); + } + + + QSize maskRenderWidget::minimumSizeHint() const + { + return pimpl_->foreground_.isNull()? QSize(400, 400) : pimpl_->foreground_.size(); + } + + + void maskRenderWidget::setPen(const QPen &pen) + { + pimpl_->pen_ = pen; + } + + + QPen maskRenderWidget::pen() const + { + return pimpl_->pen_; + } + + + void maskRenderWidget::setImage(const QImage &image) + { + QPalette palette; +#if (QT_VERSION >= 0x040100) + setAutoFillBackground(true); +#endif + palette.setBrush(backgroundRole(), QBrush(QPixmap::fromImage(image))); + setPalette(palette); + pimpl_->foreground_ = image; + QImage alpha(image.width(), image.height(),QImage::Format_Mono); + alpha.fill(0); + + pimpl_->foreground_.setAlphaChannel(alpha); + + priv::unwind(pimpl_->undo_); + priv::unwind(pimpl_->redo_); + + update(); + } + + + void maskRenderWidget::load(const QString &filename) + { + QImage alpha(filename); + // I would have liked to use KeepAspectRatio but if someone loads a + // bogus mask with a different ratio, the rest will crash. The output + // is now undefined but it won't crash. + alpha = alpha.scaled(pimpl_->foreground_.width(), pimpl_->foreground_.height(), Qt::IgnoreAspectRatio); + QImage temp(pimpl_->foreground_); + const int width(temp.width()), height(temp.height()); + for (int i = 0; i < width; ++i) + for (int j = 0; j < height; ++j) + { + QRgb rgb = temp.pixel(i, j); + temp.setPixel(i, j, QColor(qRed(rgb), qGreen(rgb), qBlue(rgb), qGray(alpha.pixel(i, j))).rgba()); + } + setAlphaMask(temp); + } + + + void maskRenderWidget::save(const QString &filename, int w, int h) + { + pimpl_->foreground_.alphaChannel().scaled(w, h, Qt::KeepAspectRatio).save(filename, "PGM"); + } + + QImage maskRenderWidget::getMask(int w, int h) const + { +// return pimpl_->foreground_.alphaChannel().scaled(w, h, Qt::KeepAspectRatio); + return pimpl_->foreground_.alphaChannel().scaled(w, h); // changed to this becouse sometimes for rounding error did not create the original size. + + } + + + void maskRenderWidget::setAlphaMask(const QImage &image) + { + pimpl_->undo_.push(pimpl_->foreground_); + pimpl_->foreground_ = image; + update(); + } + + + QImage maskRenderWidget::alphaMask() const + { + return pimpl_->foreground_; + } + + + void maskRenderWidget::undo() + { + if (!pimpl_->undo_.empty()) + { + pimpl_->redo_.push(pimpl_->foreground_); + pimpl_->foreground_ = pimpl_->undo_.top(); + pimpl_->undo_.pop(); + update(); + } + } + + + void maskRenderWidget::redo() + { + if (!pimpl_->redo_.empty()) + { + pimpl_->undo_.push(pimpl_->foreground_); + pimpl_->foreground_ = pimpl_->redo_.top(); + pimpl_->redo_.pop(); + update(); + } + } + + + void maskRenderWidget::clear() + { + pimpl_->undo_.push(pimpl_->foreground_); + priv::unwind(pimpl_->redo_); + pimpl_->foreground_.fill(QColor(Qt::transparent).rgba()); + update(); + } +}; diff --git a/src/meshlabplugins/io_epoch/maskRenderWidget.h b/src/meshlabplugins/io_epoch/maskRenderWidget.h new file mode 100644 index 000000000..84a25902e --- /dev/null +++ b/src/meshlabplugins/io_epoch/maskRenderWidget.h @@ -0,0 +1,102 @@ +#ifndef __IO_MASK_RENDER_WIDGET_INC__ +#define __IO_MASK_RENDER_WIDGET_INC__ + + +#include + + +namespace ui +{ + /*! \class maskRenderWidget + \brief A brief description + \author gmatthew + */ + class maskRenderWidget : public QWidget + { + Q_OBJECT + + struct Impl; + Impl* pimpl_; + + virtual void keyPressEvent(QKeyEvent *); + virtual void mousePressEvent(QMouseEvent *); + virtual void mouseMoveEvent(QMouseEvent *); + virtual void mouseReleaseEvent(QMouseEvent *); + virtual void paintEvent(QPaintEvent *); + + public: + /*! \brief Constructor + */ + explicit maskRenderWidget(QWidget *parent = 0); + /*! \brief Constructor + */ + explicit maskRenderWidget(const QImage &, QWidget *parent = 0); + /*! \brief Destructor + */ + virtual ~maskRenderWidget() throw(); + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; +#endif + + /*! \brief Set the drawable pen. + \param The pen object. + */ + void setPen(const QPen &pen); + /*! \brief Returns the drawable pen. + \return pen The pen object. + */ + QPen pen() const; + /*! \brief Set the background Image. + \param image The image to be set as the background. + */ + void setImage(const QImage &); + /*! \brief Load the alpha mask. + \param filename The path to the image to be used as the mask. + */ + void load(const QString &filename); + /*! \brief Save the alpha mask. + \param filename The path to the image to be used as the mask. + \param w The width of the image to save to. + \param h The height of the image to save to. + */ + void save(const QString &filename, int w, int h); + /*! \brief Get the alpha mask. + \param w The width of the image to return. + \param h The height of the image to return. + */ + QImage getMask(int w, int h) const; + /*! \brief Set the alpha mask. + \param image The image to be set as the mask. + */ + void setAlphaMask(const QImage &image); + /*! \brief Returns the alpha mask. + \return An qimage object. + */ + QImage alphaMask() const; + + public slots: + /*! \brief Undoes the last action and adds the current action to the redo stack and updates the display. If no more actions could be undone, does nothing. + + The number of times this can be done is limited only by the resources available. + */ + void undo(); + /*! \brief Redoes the last action and adds the current action to the undo stack and updates the display. If no more actions could be redone, does nothing. + + The number of times this can be done is limited only by the resources available. + */ + void redo(); + /*! \brief Clears the display. + + This action is also added to the undo actions. + */ + void clear(); + + signals: + void pointSelected(const QPoint &); + }; +}; + + +#endif diff --git a/src/meshlabplugins/io_epoch/radial_distortion.cpp b/src/meshlabplugins/io_epoch/radial_distortion.cpp new file mode 100644 index 000000000..9bb2481be --- /dev/null +++ b/src/meshlabplugins/io_epoch/radial_distortion.cpp @@ -0,0 +1,86 @@ +#include "radial_distortion.h" +#include + +using namespace std; + + +void RadialDistortion::SetParameters(vector& k, double max, int resolution) +{ + k_ = k; + max_ = max; + resolution_ = resolution; + + SetupLookupTable(max_, resolution_); +} + +void RadialDistortion::forward_map(double x1, double y1, double* x2, double* y2) const +{ + ComputeNewXY(x1-ocx_, y1-ocy_, *x2, *y2); + *x2 += ncx_; + *y2 += ncy_; +} + + +void RadialDistortion::inverse_map(double x2, double y2, double* x1, double* y1) const +{ + ComputeOldXY(x2-ncx_, y2-ncy_, *x1, *y1); + *x1 += ocx_; + *y1 += ocy_; +} + +//---------------------------------------------------------------------------- +// Relative w.r.t. center ! +void RadialDistortion::ComputeNewXY(double xo, double yo, double& xn, double& yn) const +{ + double r = (xo*xo) + (yo*yo); // r is r squared + double f = 1.0; + for (int i = 0; i < static_cast(k_.size()); i++) f += (k_[i] * pow(r, i+1)); + xn = f*xo; + yn = f*yo; +} + + +void RadialDistortion::ComputeOldXY(double xn, double yn, double& xo, double& yo) const +{ + double rn = sqrt((xn*xn) + (yn*yn)); //// r is r squared + map::const_iterator next = lookup_.upper_bound(rn); + map::const_iterator prev = next; prev--; + + // The compiler will optimise this. + double a = (*prev).first; + double b = (*next).first; + double c = rn; + double d = (*prev).second; + double e = (*next).second; + double f = ((e-d)/(b-a))*(c-a) + d; + + xo = f*xn; + yo = f*yn; +} + + +void RadialDistortion::SetupLookupTable(double max, int resolution) +{ + lookup_.clear(); + double incr = max/(double)resolution; + double value = 0.0; + double old=-1.0; + while (value < max) + { + double f = 1.0; + for (int i = 0; i < static_cast(k_.size()); i++) + f += (k_[i] * pow(value*value, i+1)); + if ((f*value)>old) + { + //cout << "R = " << value << " ,Rd = " << f*value << " ,f = " << f << endl; + lookup_[f*value] = 1.0/f; + old=f*value; + value += incr; + } + else { + //vstDebug(100)<<"vstUndoRadialDistortion::SetupLookupTable:warning RADIAL DISTORTION FOLDING BACK at "< +#include +// using namespace std; +//#include + +class RadialDistortion // : public vil_warp_mapping +{ +public: + RadialDistortion() { } + RadialDistortion(const RadialDistortion &obj): + k_(obj.k_), ocx_(obj.ocx_), ocy_(obj.ocy_), ncx_(obj.ncx_), ncy_(obj.ncy_), lookup_(obj.lookup_), max_(obj.max_), resolution_(obj.resolution_) {} + + void SetParameters(std::vector& k, double max = 2000, int resolution = 10000); + + std::vector GetParameters() { return k_; }; + + void ComputeNewXY(double xo, double yo, double& xn, double& yn) const; + void ComputeOldXY(double xn, double yn, double& xo, double& yo) const; + + // for vil_warp, which doesn't work + void forward_map(double x1, double y1, double* x2, double* y2) const; + void inverse_map(double x2, double y2, double* x1, double* y1) const; + +protected: + std::vector k_; + double ocx_; + double ocy_; + double ncx_; + double ncy_; + + void SetupLookupTable(double max, int resolution); + + std::map lookup_; + + double max_; + int resolution_; +}; diff --git a/src/meshlabplugins/io_epoch/reconstruction.cpp b/src/meshlabplugins/io_epoch/reconstruction.cpp new file mode 100644 index 000000000..8d1530446 --- /dev/null +++ b/src/meshlabplugins/io_epoch/reconstruction.cpp @@ -0,0 +1,173 @@ + +// A camera consists of K, R, t and the radial distortion parameters. +// We have an UndoRadialDistortion member (undoraddist_) which is +// initialized with the distortion parameters. + +// Furthermore we have an SVD with the P-matrix (R,t, no K). This is used +// to compute the point at infinity (direction of a line) from a point in +// the image. + +// We use vnl (of VXL: www.vxl.org) for the math but any other library will +// do. + +void vstRadialEuclideanCamera::RecomputeSvd() +{ + vnl_matrix_fixed P; + P.update(R_.transpose(), 0, 0); + P.set_column(3, -R_.transpose() * T_); + Psvd_ = vstSvd(P); +} + +// from depth and 2D point to a 3D point: + +void vstRadialEuclideanCamera::DepthTo3DPoint(const vstPoint2D &m, double depth, vstPoint3D &M) const +{ + vnl_vector_fixed m_temp = Kinv_ * m.GetVector(); + + double x, y; + undoraddist_.ComputeOldXY(m_temp(0) / m_temp(2), m_temp(1) / m_temp(2), x, y); + m_temp(0) = x; + m_temp(1) = y; + m_temp(2) = 1; + + vstPoint3D fp(T_); + vnl_vector_fixed end(Psvd_.solve(m_temp)); + vstLine3D l(fp, vstPoint3D(end)); + vnl_vector_fixed dir(l.GetPointInfinite().GetVector()); + dir.normalize(); + + M.SetVector(fp.GetVector() + depth * dir); + if (IsBehindCamera(M)) + M.SetVector(fp.GetVector() - depth * dir); +} + + + +// Undo Radial Distortion: This class has two maps to map undistorted to +// distorted coordinates and vice versa. + +#include +#include +// using namespace std; +//#include + +class vstUndoRadialDistortion // : public vil_warp_mapping +{ +public: + vstUndoRadialDistortion() { } + vstUndoRadialDistortion(const vstUndoRadialDistortion &obj): + k_(obj.k_), ocx_(obj.ocx_), ocy_(obj.ocy_), ncx_(obj.ncx_), ncy_(obj.ncy_), lookup_(obj.lookup_), max_(obj.max_), resolution_(obj.resolution_) {} + + void SetParameters(std::vector& k, double max = 2000, int resolution = 10000); + + std::vector GetParameters() { return k_; }; + + void ComputeNewXY(double xo, double yo, double& xn, double& yn) const; + void ComputeOldXY(double xn, double yn, double& xo, double& yo) const; + + // for vil_warp, which doesn't work + void forward_map(double x1, double y1, double* x2, double* y2) const; + void inverse_map(double x2, double y2, double* x1, double* y1) const; + +protected: + std::vector k_; + double ocx_; + double ocy_; + double ncx_; + double ncy_; + + void SetupLookupTable(double max, int resolution); + + std::map lookup_; + + double max_; + int resolution_; +}; + + + +#include + +using namespace std; + + +void vstUndoRadialDistortion::SetParameters(vector& k, double max, int resolution) +{ + k_ = k; + max_ = max; + resolution_ = resolution; + + SetupLookupTable(max_, resolution_); +} + +void vstUndoRadialDistortion::forward_map(double x1, double y1, double* x2, double* y2) const +{ + ComputeNewXY(x1-ocx_, y1-ocy_, *x2, *y2); + *x2 += ncx_; + *y2 += ncy_; +} + + +void vstUndoRadialDistortion::inverse_map(double x2, double y2, double* x1, double* y1) const +{ + ComputeOldXY(x2-ncx_, y2-ncy_, *x1, *y1); + *x1 += ocx_; + *y1 += ocy_; +} + +//---------------------------------------------------------------------------- +// Relative w.r.t. center ! +void vstUndoRadialDistortion::ComputeNewXY(double xo, double yo, double& xn, double& yn) const +{ + double r = (xo*xo) + (yo*yo); // r is r squared + double f = 1.0; + for (int i = 0; i < k_.size(); i++) f += (k_[i] * pow(r, i+1)); + xn = f*xo; + yn = f*yo; +} + + +void vstUndoRadialDistortion::ComputeOldXY(double xn, double yn, double& xo, double& yo) const +{ + double rn = sqrt((xn*xn) + (yn*yn)); //// r is r squared + map::const_iterator next = lookup_.upper_bound(rn); + map::const_iterator prev = next; prev--; + + // The compiler will optimise this. + double a = (*prev).first; + double b = (*next).first; + double c = rn; + double d = (*prev).second; + double e = (*next).second; + double f = ((e-d)/(b-a))*(c-a) + d; + + xo = f*xn; + yo = f*yn; +} + + +void vstUndoRadialDistortion::SetupLookupTable(double max, int resolution) +{ + lookup_.clear(); + double incr = max/(double)resolution; + double value = 0.0; + double old=-1.0; + while (value < max) + { + double f = 1.0; + for (int i = 0; i < k_.size(); i++) + f += (k_[i] * pow(value*value, i+1)); + if ((f*value)>old) + { + //cout << "R = " << value << " ,Rd = " << f*value << " ,f = " << f << endl; + lookup_[f*value] = 1.0/f; + old=f*value; + value += incr; + } + else { + //vstDebug(100)<<"vstUndoRadialDistortion::SetupLookupTable:warning RADIAL DISTORTION FOLDING BACK at "< +#include +//#include +//#include +//#include +//#include +// +// +//// temporaneamente prendo la versione corrente dalla cartella test +//#include +//#include +//#include +//#include +//#include +//#include +//#include +// +//#include +//#include + +//#include "epoch.h" +//#include "radial_distortion.h" +//#include "epoch_camera.h" +#include +#include "scalar_image.h" + +#include + +using namespace std; +/* + +Images with bit-depths greater than 8bpp and up to 31bpp are supported by the JJ2000 codec if they are stored in PGX files +(one file per component). PGX is a custom monochrome file format invented specifically to simplify the use of JPEG 2000 +with images of different bit-depths in the range of 1 to 31 bits per pixel. + +The file consists of a one line text header followed by the (raw) data. + +Header: "PG"+ ws ++ ws +[sign]+ws + +" "++" "++[compressed_size]'\n' + +where: + + * ws (white-spaces) is any combination of characters ' ' and '\t'. + * endianess equals "LM" or "ML"(resp. little-endian or big-endian) + * sign equals "+" or "-" (resp. unsigned or signed). If omited, values are supposed to be unsigned. + * bit-depth that can be any number between 1 and 31. This number must take into account the eventual sign bit. + * width and height are the image dimensions (in pixels). + +Data: The image binary values appear one after the other (in raster order) immediately after the last header character ('\n') +and are byte-aligned (they are packed into 1,2 or 4 bytes per sample, depending upon the bit-depth value). +*/ +template <> +bool ScalarImage::Open(const char *filename) +{ + FILE *fp=fopen(filename,"rb"); + if(!fp) return false; + char buf[256]; + fgets(buf,255,fp); + qDebug("Header of %s is '%s'",filename,buf); + float ll,lh; + int depth; + char mode; + int compressed_size=0; + sscanf(buf,"PG LM %i %i %i %c %f %f %i",&depth,&w,&h,&mode,&ll,&lh,&compressed_size); + qDebug("image should be of %i x %i %i depth and with range in %f -- %f in mode %c",w,h,depth,ll,lh,mode); + if(depth!=16) + { + qDebug("Wrong depth of image 16 bit expected"); + return false; + } + if (mode != 'l' && mode != 'L') + { + qDebug("Wrong mode, expected l or L"); + return false; + } + + if (mode == 'l') + { + vector bb(w*h); + fread(&*bb.begin(),w*h,sizeof(short),fp); + v.resize(w*h); + for(int i =0; i +bool ScalarImage::Open(const char *filename) +{ + FILE *fp=fopen(filename,"rb"); + if(!fp) return false; + char buf[256]; + fgets(buf,255,fp); + qDebug("Header of %s is '%s'",filename,buf); + int depth; + char mode = ' '; + int compressed_size=0; + int nrscan = sscanf(buf,"PG LM %i %i %i %c %i",&depth,&w,&h,&mode,&compressed_size); + if (nrscan == 3) + qDebug("image should be of %i x %i %i depth ",w,h,depth); + else + qDebug("compressed image of %i x %i %i depth ",w,h,depth); + if(depth!=8) + { + qDebug("Wrong depth of image: 8 bit expected"); + return false; + } + + if (mode != 'C') + { + v.resize(w*h); + fread(&*v.begin(),w*h,sizeof(unsigned char),fp); + } + else + { + char* compressed_buffer = new char[compressed_size]; + fread(compressed_buffer,compressed_size,1,fp); + // decompress! + unsigned int mysize = w*h; + v.resize(w*h); + BZ2_bzBuffToBuffDecompress((char*)&*v.begin(), &mysize, compressed_buffer, compressed_size, 0, 0); + if (mysize != w*h) + { + qDebug("This is very wrong. The uncompressed size is not the expected size"); + return false; + } + } + + fclose(fp); + return true; +} + + +template <> +ScalarImage::ScalarImage(QImage img) +{ + resize(img.width(),img.height()); + + for(int y=0;y +bool ScalarImage::Subsample(const int factor, ScalarImage &fli) +{ + resize(fli.w/factor,fli.h/factor); + + for(int i=0;i +#include +#include +/* +Very simple class to store a bitmap of floating point values. +*/ +template +class ScalarImage +{ +public: + std::vector v; + int w,h; + void resize(int _w, int _h) { w=_w; h=_h; v.resize(w*h);} + bool Open(const char *filename); + ScalarType &Val(int x,int y) { + assert(x>=0 && x=0 && y=0 && x=0 && y maximum) + maximum = Val(xx, yy); + + Dilated.Val(x, y) = maximum; + } + } + + ScalarImage(QImage img); + ScalarImage(){}; + bool Subsample(const int factor, ScalarImage &fli); + static QPixmap colorizedScaledToHeight(const int desiredH, ScalarImage &fli, float colormax=10) + { + assert(fli.h>desiredH); + int factor = fli.h / desiredH; + + int newW=fli.w/factor -1; + int newH=fli.h/factor -1; + QImage newImage(newW,newH,QImage::Format_RGB32); + + for(int i=0;i FloatImage; +typedef ScalarImage CharImage; + + +#endif diff --git a/src/meshlabplugins/io_epoch/ui/v3dImportDialog.ui b/src/meshlabplugins/io_epoch/ui/v3dImportDialog.ui new file mode 100644 index 000000000..ed748b1f7 --- /dev/null +++ b/src/meshlabplugins/io_epoch/ui/v3dImportDialog.ui @@ -0,0 +1,678 @@ + + v3dImportDialog + + + + 0 + 0 + 792 + 606 + + + + + 0 + 0 + + + + V3D Import Settings + + + + + + + + TextLabel + + + + + + + + 0 + 0 + + + + TextLabel + + + + + + + + + SubSample + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + TextLabel + + + + + + + <html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Set the subsample factor:</p><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 1 the image is not resized</p><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 2 image is halved <span style=" font-style:italic;">(one point every 4)</span></p><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 3 image is reduced to one third <span style=" font-style:italic;">(one point every 9)</span></p></body></html> + + + + + + + + + + + Minimum Count + + + + + + + Qt::Horizontal + + + + 91 + 20 + + + + + + + + <html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Set the minimum number of match for a sample to be accepted</p><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">3 means that only samples that had been found a correspondence with other 2 images or more are considered</p></body></html> + + + + + + + 0 + + + + + + 0 + 0 + + + + 10 + + + Qt::Horizontal + + + + + + + TextLabel + + + + + + + + + + + + + Minimum Angle + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 1 + + + 90.000000000000000 + + + 5.000000000000000 + + + 75.000000000000000 + + + + + + + + + + + Feature Aware Smoothing + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 0 + + + 20.000000000000000 + + + 3.000000000000000 + + + + + + + + + + + Qt::RightToLeft + + + Remove pieces less than + + + true + + + + + + + + 0 + 0 + + + + When enabled, all the floating pieces smaller than the indicated percentage are deleted. Unit is the diagonal of the bounding box of the object + + + 0 + + + 25.000000000000000 + + + 5.000000000000000 + + + 5.000000000000000 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + true + + + Qt::RightToLeft + + + Close Holes less than + + + + + + + 5 + + + 10 + + + + + + + + + + + 1 + + + 6 + + + 1 + + + 5 + + + 2 + + + Qt::Horizontal + + + + + + + 1 + + + 10 + + + 3 + + + + + + + Size: + + + + + + + 5 x 5 + + + + + + + 1 + + + 5 + + + + + + + Num. passes: + + + + + + + 1 + + + 6 + + + 1 + + + 5 + + + 2 + + + Qt::Horizontal + + + + + + + 5 x 5 + + + + + + + Num. passes: + + + + + + + Depth Filter + + + + + + + Size: + + + + + + + Dilation + + + true + + + + + + + Erosion + + + true + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Perform a fast, fixed resolution merging of all the range maps. If unchecked all the rangemaps are simply put in the same space without merging them.</p></body></html> + + + Qt::RightToLeft + + + Fast merge + + + + + + + Resolution: Min + + + + + + + 1 + + + 5 + + + 3 + + + Qt::Horizontal + + + + + + + Max + + + + + + + + + + + Select + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 3 + + + + + + + + + + + Scaling Factor + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 1.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Qt::Horizontal + + + + 131 + 31 + + + + + + + + OK + + + + + + + Process all selected range maps and save them as separated ply + + + Export as PLY + + + + + + + Cancel + + + + + + + + + + + + 0 + 0 + + + + 3 + + + + + + + + + + + + okButton + clicked() + v3dImportDialog + accept() + + + 554 + 682 + + + 342 + 405 + + + + + cancelButton + clicked() + v3dImportDialog + reject() + + + 637 + 682 + + + 425 + 433 + + + + + removeSmallCCCheckBox + toggled(bool) + maxCCDiagSpinBox + setEnabled(bool) + + + 348 + 584 + + + 637 + 584 + + + + + diff --git a/src/meshlabplugins/io_epoch/v3dImportDialog.cpp b/src/meshlabplugins/io_epoch/v3dImportDialog.cpp new file mode 100644 index 000000000..64afe64b7 --- /dev/null +++ b/src/meshlabplugins/io_epoch/v3dImportDialog.cpp @@ -0,0 +1,246 @@ +/**************************************************************************** +* VCGLib o o * +* Visual and Computer Graphics Library o o * +* _ O _ * +* Copyright(C) 2004 \/)\/ * +* Visual Computing Lab /\/| * +* ISTI - Italian National Research Council | * +* \ * +* All rights reserved. * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License (http://www.gnu.org/licenses/gpl.txt) * +* for more details. * +* * +****************************************************************************/ +#include +#include +#include + +#include "v3dImportDialog.h" +#include "maskImageWidget.h" +using namespace vcg; + +// small helper function for generating a color ramp pixmap to be used in the import dialog. + +QPixmap generateColorRamp() +{ + const int width=100; + const int height=15; + Color4b ramp; + QImage newImage(width,height,QImage::Format_RGB32); + + for(int x=0;xcreated) + { + er=_er; + return; + } + + er=_er; + erCreated=er->created; + infoLabel->setText(er->name + " - " + er->author + " - " + er->created); + + imageTableWidget->clear(); + imageTableWidget->setRowCount(er->modelList.size()); + imageTableWidget->setColumnCount(4); + //imageTableWidget->setColumnWidth (1,64); + imageTableWidget->setSelectionBehavior (QAbstractItemView::SelectRows); + imageTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + imageTableWidget->setMinimumWidth(64*8); + + rangeLabel->setPixmap(generateColorRamp()); + rangeLabel->setMaximumHeight(10); + rangeLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + rangeLabel->setScaledContents(true); + + minCountSlider->setMaximumHeight(12); + + int i; + for(i=0; imodelList.size() ;++i) + { + cb(i*100/er->modelList.size(),"Reading images"); + + QString ThumbImgName=EpochModel::ThumbName(er->modelList[i].textureName); + QString ThumbCntName=EpochModel::ThumbName(er->modelList[i].countName); + + imageTableWidget->setRowHeight (i,64); + QTableWidgetItem *emptyHeaderItem = new QTableWidgetItem(i*2); + QTableWidgetItem *texNameHeaderItem = new QTableWidgetItem(er->modelList[i].textureName,i); + imageTableWidget->setItem(i, 0, texNameHeaderItem); + imageTableWidget->setItem(i, 1, emptyHeaderItem); + QLabel *imageLabel = new QLabel(imageTableWidget); + if(!QFile::exists(ThumbImgName)) + { + QPixmap(er->modelList[i].textureName).scaledToHeight(64).save(ThumbImgName,"jpg"); + if(!QFile::exists(ThumbImgName)) + QMessageBox::warning(this,"Error in Thumb creation", + QString("Unable to create '%1' from '%2'").arg(ThumbImgName),er->modelList[i].textureName); + } + + imageLabel->setPixmap(QPixmap(EpochModel::ThumbName(er->modelList[i].textureName))); + imageTableWidget->setCellWidget(i,1,imageLabel); + if(QFile::exists(er->modelList[i].maskName)) + { + QTableWidgetItem *emptyHeaderItem = new QTableWidgetItem(i*4); + imageTableWidget->setItem(i, 2, emptyHeaderItem); + QLabel *maskLabel = new QLabel(imageTableWidget); + maskLabel->setPixmap(QPixmap(er->modelList[i].maskName).scaledToHeight(64)); + imageTableWidget->setCellWidget(i,2,maskLabel); + } + else + { + QTableWidgetItem *maskHeaderItem = new QTableWidgetItem(QString("double click for\nediting the mask"),i*3); + imageTableWidget->setItem(i, 2, maskHeaderItem); + } + + if(!QFile::exists(ThumbCntName)) + { + CharImage chi; + bool ret=chi.Open(er->modelList[i].countName.toAscii()); + if(!ret) QMessageBox::warning(this,"Error in Thumb creation",QString("Unable to create '%1' from '%2'").arg(ThumbCntName,er->modelList[i].textureName)); + + CharImage::colorizedScaledToHeight(64,chi).save(ThumbCntName,"jpg"); + if(!QFile::exists(ThumbCntName)) + QMessageBox::warning(this,"Error in Thumb creation",QString("Unable to create '%1' from '%2'").arg(ThumbCntName,er->modelList[i].textureName)); + } + QLabel *countLabel = new QLabel(imageTableWidget); + countLabel->setPixmap(QPixmap(ThumbCntName)); + QPixmap tmp(ThumbCntName); + if(tmp.isNull()) + QMessageBox::warning(this,"Error in Thumb creation",QString("Null Pixmap '%1'").arg(ThumbCntName)); + imageTableWidget->setCellWidget(i,3,countLabel); + } + +cb(100,"Completed Image Reading."); + +show(); // necessary to make the size of the image preview correct. +imageTableWidget->setItemSelected(imageTableWidget->item(0,0),true); +imageTableWidget->setItemSelected(imageTableWidget->item(0,1),true); +imageTableWidget->setItemSelected(imageTableWidget->item(0,2),true); +//on_imageTableWidget_itemSelectionChanged(); + +} + +void v3dImportDialog::on_imageTableWidget_itemSelectionChanged() +{ + if(imageTableWidget->selectedItems().size()==3) + { + int row= imageTableWidget->row(imageTableWidget->selectedItems().first()); + QPixmap tmp(er->modelList[row].textureName); + imgSize=tmp.size(); + previewLabel->setPixmap(tmp.scaled(previewLabel->size(),Qt::KeepAspectRatio) ); + on_subsampleSpinBox_valueChanged(subsampleSpinBox->value()); + } +} +void v3dImportDialog::on_plyButton_clicked() +{ + exportToPLY = true; + accept(); +} +void v3dImportDialog::on_selectButton_clicked() +{ + int itemNum=imageTableWidget->rowCount(); + int modVal=subsampleSequenceSpinBox->value(); + if(modVal==0) return; + + for(int i=0;isetRangeSelected(QTableWidgetSelectionRange(i,0,i,2),true); +} + +void v3dImportDialog::on_imageTableWidget_itemClicked(QTableWidgetItem * item ) +{ + int row= imageTableWidget->row(item); + previewLabel->setPixmap( + QPixmap(er->modelList[row].textureName).scaled(previewLabel->size(),Qt::KeepAspectRatio) ); + +} + +void v3dImportDialog::on_imageTableWidget_itemDoubleClicked(QTableWidgetItem * item ) +{ + int row= imageTableWidget->row(item); + int col= imageTableWidget->column(item); + if(col!=2) return; + qDebug("DoubleClicked on image %s",qPrintable(er->modelList[row].textureName)); + QImage ttt(er->modelList[row].textureName); + qDebug("'%s' %i x %i",qPrintable(er->modelList[row].textureName),ttt.width(),ttt.height()); + //ui::maskImageWidget masker(QImage(er->modelList[row].textureName)); + ui::maskImageWidget masker(ttt); + if (QFile::exists(er->modelList[row].maskName)) + masker.loadMask(er->modelList[row].maskName); + + QImage mymask; + if (masker.exec() == QDialog::Accepted) mymask = masker.getMask(); + if (!mymask.isNull()) + { + mymask.save(er->modelList[row].maskName,"png"); + QLabel *imageLabel = new QLabel(imageTableWidget); + imageLabel->setPixmap(QPixmap(er->modelList[row].maskName).scaledToHeight(64)); + imageTableWidget->itemAt(row,2)->setText(""); + imageTableWidget->setCellWidget(row,2,imageLabel); + } +} + + + +void v3dImportDialog::on_minCountSpinBox_valueChanged(int val) +{ + if(minCountSlider->value()!=val) minCountSlider->setValue(val); +} + +void v3dImportDialog::on_minCountSlider_valueChanged(int val) +{ + if(minCountSpinBox->value()!=val) minCountSpinBox->setValue(val); +} + +void v3dImportDialog::on_subsampleSpinBox_valueChanged(int) +{ + int ss=subsampleSpinBox->value(); + if(ss==0) + { + subsampleSpinBox->setValue(1); + return; + } + imgSizeLabel->setText(QString("(%1 x %2) -> (%3 x %4)").arg(imgSize.width()).arg(imgSize.height()).arg(imgSize.width()/ss).arg(imgSize.height()/ss) ); +} + +void v3dImportDialog::on_mergeResolutionSpinBox_valueChanged(int) +{ + fastMergeCheckBox->setChecked(true); // if someone touch the slider check the fast merging box +} + +void v3dImportDialog::dilationSizeChanged(int size) +{ + int winsize = size * 2 + 1; + QString str = QString("%1 x %2").arg(winsize).arg(winsize); + lblDilationSizeValue->setText(str); +} + +void v3dImportDialog::erosionSizeChanged(int size) +{ + int winsize = size * 2 + 1; + QString str = QString("%1 x %2").arg(winsize).arg(winsize); + lblErosionSizeValue->setText(str); +} diff --git a/src/meshlabplugins/io_epoch/v3dImportDialog.h b/src/meshlabplugins/io_epoch/v3dImportDialog.h new file mode 100644 index 000000000..2409a021b --- /dev/null +++ b/src/meshlabplugins/io_epoch/v3dImportDialog.h @@ -0,0 +1,53 @@ +#ifndef V3DIMPORTER_DIALOG_H +#define V3DIMPORTER_DIALOG_H + +// for options on decimator +#include +#include "ui_v3dImportDialog.h" +#include "epoch_reconstruction.h" + +class v3dImportDialog : public QDialog, public Ui::v3dImportDialog { + +Q_OBJECT + +public: + v3dImportDialog() : QDialog() + { + setupUi( this ); + subsampleSpinBox->setValue(2); + minCountSpinBox->setValue(3); + + // connections + connect(dilationSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(dilationSizeChanged(int))); + connect(erosionSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(erosionSizeChanged(int))); + + er=0; + exportToPLY=false; + } + +public: + void setEpochReconstruction(EpochReconstruction *_er,vcg::CallBackPos *cb); + bool exportToPLY; /// when true all the selected range maps are exported as separated ply + +public slots: +void on_selectButton_clicked(); +void on_imageTableWidget_itemClicked(QTableWidgetItem * item ); +void on_imageTableWidget_itemSelectionChanged(); +void on_imageTableWidget_itemDoubleClicked(QTableWidgetItem * item ); +void on_plyButton_clicked(); +private: +EpochReconstruction *er; +QString erCreated; +QSize imgSize; + + +private slots: + void on_mergeResolutionSpinBox_valueChanged(int); + void on_subsampleSpinBox_valueChanged(int); + void on_minCountSlider_valueChanged(int); + void on_minCountSpinBox_valueChanged(int); + void dilationSizeChanged(int); + void erosionSizeChanged(int); +}; + +#endif