// This file is part of libigl, a simple c++ geometry processing library. // // Copyright (C) 2013 Alec Jacobson <alecjacobson@gmail.com> // // This Source Code Form is subject to the terms of the Mozilla Public License // v. 2.0. If a copy of the MPL was not distributed with this file, You can // obtain one at http://mozilla.org/MPL/2.0/. #ifndef IGL_MATLAB_MATLAB_WORKSPACE_H #define IGL_MATLAB_MATLAB_WORKSPACE_H #include <Eigen/Dense> #include <Eigen/Sparse> #include <mat.h> #include <string> #include <vector> namespace igl { namespace matlab { // It would be really great to replicate this for a simple XML-based // workspace. // // Class which contains data of a matlab workspace which can be written to a // .mat file and loaded from matlab // // This depends on matlab at compile time (though it shouldn't necessarily // have to) but it does not depend on running the matlab engine at run-time. // // Known bugs: Treats all matrices as doubles (this may actually be desired // for some "index" matrices since matlab's sparse command takes doubles // rather than int class matrices). It is of course not desired when dealing // with logicals or uint's for images. class MatlabWorkspace { private: // KNOWN BUG: Why not use a map? Any reason to allow duplicate names? // // List of names std::vector<std::string> names; // List of data pointers std::vector<mxArray*> data; public: MatlabWorkspace(); ~MatlabWorkspace(); // Clear names and data of variables in workspace inline void clear(); // Save current list of variables // // Inputs: // path path to .mat file // Returns true on success, false on failure inline bool write(const std::string & path) const; // Load list of variables from .mat file // // Inputs: // path path to .mat file // Returns true on success, false on failure inline bool read(const std::string & path); // Assign data to a variable name in the workspace // // Template: // DerivedM eigen matrix (e.g. MatrixXd) // Inputs: // M data (usually a matrix) // name variable name to save into work space // Returns true on success, false on failure // // Known Bugs: Assumes Eigen is using column major ordering template <typename DerivedM> inline MatlabWorkspace& save( const Eigen::PlainObjectBase<DerivedM>& M, const std::string & name); // Template: // MT sparse matrix type (e.g. double) template <typename MT> inline MatlabWorkspace& save( const Eigen::SparseMatrix<MT>& M, const std::string & name); // Templates: // ScalarM scalar type, e.g. double template <typename ScalarM> inline MatlabWorkspace& save( const std::vector<std::vector<ScalarM> > & vM, const std::string & name); // Templates: // ScalarV scalar type, e.g. double template <typename ScalarV> inline MatlabWorkspace& save( const std::vector<ScalarV> & vV, const std::string & name); // NOTE: Eigen stores quaternions coefficients as (i,j,k,1), but most of // our matlab code stores them as (1,i,j,k) This takes a quaternion and // saves it as a (1,i,j,k) row vector // // Templates: // Q quaternion type template <typename Q> inline MatlabWorkspace& save( const Eigen::Quaternion<Q> & q, const std::string & name); inline MatlabWorkspace& save( const double d, const std::string & name); // Same as save() but adds 1 to each element, useful for saving "index" // matrices like lists of faces or elements template <typename DerivedM> inline MatlabWorkspace& save_index( const Eigen::DenseBase<DerivedM>& M, const std::string & name); template <typename ScalarM> inline MatlabWorkspace& save_index( const std::vector<std::vector<ScalarM> > & vM, const std::string & name); template <typename ScalarV> inline MatlabWorkspace& save_index( const std::vector<ScalarV> & vV, const std::string & name); // Find a certain matrix by name. // // KNOWN BUG: Outputs the first found (not necessarily unique lists). // // Template: // DerivedM eigen matrix (e.g. MatrixXd) // Inputs: // name exact name of matrix as string // Outputs: // M matrix // Returns true only if found. template <typename DerivedM> inline bool find( const std::string & name, Eigen::PlainObjectBase<DerivedM>& M); template <typename MT> inline bool find( const std::string & name, Eigen::SparseMatrix<MT>& M); inline bool find( const std::string & name, double & d); inline bool find( const std::string & name, int & v); // Subtracts 1 from all entries template <typename DerivedM> inline bool find_index( const std::string & name, Eigen::PlainObjectBase<DerivedM>& M); }; } } // Implementation // Be sure that this is not compiled into libigl.a // http://stackoverflow.com/a/3318993/148668 // IGL #include "igl/list_to_matrix.h" // MATLAB #include "mat.h" // STL #include <iostream> #include <algorithm> #include <vector> inline igl::matlab::MatlabWorkspace::MatlabWorkspace(): names(), data() { } inline igl::matlab::MatlabWorkspace::~MatlabWorkspace() { // clean up data clear(); } inline void igl::matlab::MatlabWorkspace::clear() { for_each(data.begin(),data.end(),&mxDestroyArray); data.clear(); names.clear(); } inline bool igl::matlab::MatlabWorkspace::write(const std::string & path) const { using namespace std; MATFile * mat_file = matOpen(path.c_str(), "w"); if(mat_file == NULL) { fprintf(stderr,"Error opening file %s\n",path.c_str()); return false; } assert(names.size() == data.size()); // loop over names and data for(int i = 0;i < (int)names.size(); i++) { // Put variable as LOCAL variable int status = matPutVariable(mat_file,names[i].c_str(), data[i]); if(status != 0) { cerr<<"^MatlabWorkspace::save Error: matPutVariable ("<<names[i]<< ") failed"<<endl; return false; } } if(matClose(mat_file) != 0) { fprintf(stderr,"Error closing file %s\n",path.c_str()); return false; } return true; } inline bool igl::matlab::MatlabWorkspace::read(const std::string & path) { using namespace std; MATFile * mat_file; mat_file = matOpen(path.c_str(), "r"); if (mat_file == NULL) { cerr<<"Error: failed to open "<<path<<endl; return false; } int ndir; const char ** dir = (const char **)matGetDir(mat_file, &ndir); if (dir == NULL) { cerr<<"Error reading directory of file "<< path<<endl; return false; } mxFree(dir); // Must close and reopen if(matClose(mat_file) != 0) { cerr<<"Error: failed to close file "<<path<<endl; return false; } mat_file = matOpen(path.c_str(), "r"); if (mat_file == NULL) { cerr<<"Error: failed to open "<<path<<endl; return false; } /* Read in each array. */ for (int i=0; i<ndir; i++) { const char * name; mxArray * mx_data = matGetNextVariable(mat_file, &name); if (mx_data == NULL) { cerr<<"Error: matGetNextVariable failed in "<<path<<endl; return false; } const int dims = mxGetNumberOfDimensions(mx_data); assert(dims == 2); if(dims != 2) { fprintf(stderr,"Variable '%s' has %d ≠ 2 dimensions. Skipping\n", name,dims); mxDestroyArray(mx_data); continue; } // don't destroy names.push_back(name); data.push_back(mx_data); } if(matClose(mat_file) != 0) { cerr<<"Error: failed to close file "<<path<<endl; return false; } return true; } // Treat everything as a double template <typename DerivedM> inline igl::matlab::MatlabWorkspace& igl::matlab::MatlabWorkspace::save( const Eigen::PlainObjectBase<DerivedM>& M, const std::string & name) { using namespace std; const int m = M.rows(); const int n = M.cols(); mxArray * mx_data = mxCreateDoubleMatrix(m,n,mxREAL); data.push_back(mx_data); names.push_back(name); // Copy data immediately // Use Eigen's map and cast to copy Eigen::Map< Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic> > map(mxGetPr(mx_data),m,n); map = M.template cast<double>(); return *this; } // Treat everything as a double template <typename MT> inline igl::matlab::MatlabWorkspace& igl::matlab::MatlabWorkspace::save( const Eigen::SparseMatrix<MT>& M, const std::string & name) { using namespace std; const int m = M.rows(); const int n = M.cols(); // THIS WILL NOT WORK FOR ROW-MAJOR assert(n==M.outerSize()); const int nzmax = M.nonZeros(); mxArray * mx_data = mxCreateSparse(m, n, nzmax, mxREAL); data.push_back(mx_data); names.push_back(name); // Copy data immediately double * pr = mxGetPr(mx_data); mwIndex * ir = mxGetIr(mx_data); mwIndex * jc = mxGetJc(mx_data); // Iterate over outside int k = 0; for(int j=0; j<M.outerSize();j++) { jc[j] = k; // Iterate over inside for(typename Eigen::SparseMatrix<MT>::InnerIterator it (M,j); it; ++it) { pr[k] = it.value(); ir[k] = it.row(); k++; } } jc[M.outerSize()] = k; return *this; } template <typename ScalarM> inline igl::matlab::MatlabWorkspace& igl::matlab::MatlabWorkspace::save( const std::vector<std::vector<ScalarM> > & vM, const std::string & name) { Eigen::MatrixXd M; list_to_matrix(vM,M); return this->save(M,name); } template <typename ScalarV> inline igl::matlab::MatlabWorkspace& igl::matlab::MatlabWorkspace::save( const std::vector<ScalarV> & vV, const std::string & name) { Eigen::MatrixXd V; list_to_matrix(vV,V); return this->save(V,name); } template <typename Q> inline igl::matlab::MatlabWorkspace& igl::matlab::MatlabWorkspace::save( const Eigen::Quaternion<Q> & q, const std::string & name) { Eigen::Matrix<Q,1,4> qm; qm(0,0) = q.w(); qm(0,1) = q.x(); qm(0,2) = q.y(); qm(0,3) = q.z(); return save(qm,name); } inline igl::matlab::MatlabWorkspace& igl::matlab::MatlabWorkspace::save( const double d, const std::string & name) { Eigen::VectorXd v(1); v(0) = d; return save(v,name); } template <typename DerivedM> inline igl::matlab::MatlabWorkspace& igl::matlab::MatlabWorkspace::save_index( const Eigen::DenseBase<DerivedM>& M, const std::string & name) { DerivedM Mp1 = M; Mp1.array() += 1; return this->save(Mp1,name); } template <typename ScalarM> inline igl::matlab::MatlabWorkspace& igl::matlab::MatlabWorkspace::save_index( const std::vector<std::vector<ScalarM> > & vM, const std::string & name) { Eigen::MatrixXd M; list_to_matrix(vM,M); return this->save_index(M,name); } template <typename ScalarV> inline igl::matlab::MatlabWorkspace& igl::matlab::MatlabWorkspace::save_index( const std::vector<ScalarV> & vV, const std::string & name) { Eigen::MatrixXd V; list_to_matrix(vV,V); return this->save_index(V,name); } template <typename DerivedM> inline bool igl::matlab::MatlabWorkspace::find( const std::string & name, Eigen::PlainObjectBase<DerivedM>& M) { using namespace std; const int i = std::find(names.begin(), names.end(), name)-names.begin(); if(i>=(int)names.size()) { return false; } assert(i<=(int)data.size()); mxArray * mx_data = data[i]; assert(!mxIsSparse(mx_data)); assert(mxGetNumberOfDimensions(mx_data) == 2); //cout<<name<<": "<<mxGetM(mx_data)<<" "<<mxGetN(mx_data)<<endl; const int m = mxGetM(mx_data); const int n = mxGetN(mx_data); // Handle vectors: in the sense that anything found becomes a column vector, // whether it was column vector, row vector or matrix if(DerivedM::IsVectorAtCompileTime) { assert(m==1 || n==1 || (m==0 && n==0)); M.resize(m*n,1); }else { M.resize(m,n); } assert(mxGetNumberOfElements(mx_data) == M.size()); // Use Eigen's map and cast to copy M = Eigen::Map< Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic> > (mxGetPr(mx_data),M.rows(),M.cols()).cast<typename DerivedM::Scalar>(); return true; } template <typename MT> inline bool igl::matlab::MatlabWorkspace::find( const std::string & name, Eigen::SparseMatrix<MT>& M) { using namespace std; using namespace Eigen; const int i = std::find(names.begin(), names.end(), name)-names.begin(); if(i>=(int)names.size()) { return false; } assert(i<=(int)data.size()); mxArray * mx_data = data[i]; // Handle boring case where matrix is actually an empty dense matrix if(mxGetNumberOfElements(mx_data) == 0) { M.resize(0,0); return true; } assert(mxIsSparse(mx_data)); assert(mxGetNumberOfDimensions(mx_data) == 2); //cout<<name<<": "<<mxGetM(mx_data)<<" "<<mxGetN(mx_data)<<endl; const int m = mxGetM(mx_data); const int n = mxGetN(mx_data); // TODO: It should be possible to directly load the data into the sparse // matrix without going through the triplets // Copy data immediately double * pr = mxGetPr(mx_data); mwIndex * ir = mxGetIr(mx_data); mwIndex * jc = mxGetJc(mx_data); vector<Triplet<MT> > MIJV; const int nnz = mxGetNzmax(mx_data); MIJV.reserve(nnz); // Iterate over outside int k = 0; for(int j=0; j<n;j++) { // Iterate over inside while(k<(int)jc[j+1]) { //cout<<ir[k]<<" "<<j<<" "<<pr[k]<<endl; assert((int)ir[k]<m); assert((int)j<n); MIJV.push_back(Triplet<MT >(ir[k],j,pr[k])); k++; } } M.resize(m,n); M.setFromTriplets(MIJV.begin(),MIJV.end()); return true; } inline bool igl::matlab::MatlabWorkspace::find( const std::string & name, int & v) { using namespace std; const int i = std::find(names.begin(), names.end(), name)-names.begin(); if(i>=(int)names.size()) { return false; } assert(i<=(int)data.size()); mxArray * mx_data = data[i]; assert(!mxIsSparse(mx_data)); assert(mxGetNumberOfDimensions(mx_data) == 2); //cout<<name<<": "<<mxGetM(mx_data)<<" "<<mxGetN(mx_data)<<endl; assert(mxGetNumberOfElements(mx_data) == 1); copy( mxGetPr(mx_data), mxGetPr(mx_data)+mxGetNumberOfElements(mx_data), &v); return true; } inline bool igl::matlab::MatlabWorkspace::find( const std::string & name, double & d) { using namespace std; const int i = std::find(names.begin(), names.end(), name)-names.begin(); if(i>=(int)names.size()) { return false; } assert(i<=(int)data.size()); mxArray * mx_data = data[i]; assert(!mxIsSparse(mx_data)); assert(mxGetNumberOfDimensions(mx_data) == 2); //cout<<name<<": "<<mxGetM(mx_data)<<" "<<mxGetN(mx_data)<<endl; assert(mxGetNumberOfElements(mx_data) == 1); copy( mxGetPr(mx_data), mxGetPr(mx_data)+mxGetNumberOfElements(mx_data), &d); return true; } template <typename DerivedM> inline bool igl::matlab::MatlabWorkspace::find_index( const std::string & name, Eigen::PlainObjectBase<DerivedM>& M) { if(!find(name,M)) { return false; } M.array() -= 1; return true; } //template <typename Data> //bool igl::matlab::MatlabWorkspace::save(const Data & M, const std::string & name) //{ // using namespace std; // // If I don't know the type then I can't save it // cerr<<"^MatlabWorkspace::save Error: Unknown data type. "<< // name<<" not saved."<<endl; // return false; //} #endif