From 23a0b2f2145705b2baa44877ed8b974e306e5f83 Mon Sep 17 00:00:00 2001 From: Howard Butler Date: Wed, 28 Jun 2017 11:34:35 -0500 Subject: [PATCH] working readers.matlab --- doc/stages/readers.matlab.rst | 51 ++++++++ pdal/StageFactory.cpp | 2 + plugins/matlab/CMakeLists.txt | 31 +++-- plugins/matlab/filters/Script.cpp | 30 +++++ plugins/matlab/filters/Script.hpp | 1 + plugins/matlab/io/MatlabReader.cpp | 157 +++++++++++++---------- plugins/matlab/io/MatlabReader.hpp | 23 ++-- plugins/matlab/io/MatlabWriter.cpp | 3 +- plugins/matlab/io/MatlabWriter.hpp | 1 + plugins/matlab/test/MatlabReaderTest.cpp | 59 +++++++++ test/data/matlab/autzen.mat | Bin 0 -> 26629 bytes 11 files changed, 260 insertions(+), 98 deletions(-) create mode 100644 doc/stages/readers.matlab.rst create mode 100644 plugins/matlab/test/MatlabReaderTest.cpp create mode 100644 test/data/matlab/autzen.mat diff --git a/doc/stages/readers.matlab.rst b/doc/stages/readers.matlab.rst new file mode 100644 index 0000000000..ab41e18096 --- /dev/null +++ b/doc/stages/readers.matlab.rst @@ -0,0 +1,51 @@ +.. _readers.matlab: + +readers.matlab +============== + +The **Matlab Reader** supports readers Matlab ``.mat`` files. Data +must be in a `Matlab struct`_, with field names that correspond to +:ref:`dimensions` names. No ability to provide a name map is yet +provided. + +Additionally, each array in the struct should ideally have the +same number of points. The reader takes its number of points +from the first array in the struct. If the array has fewer +elements than the first array in the struct, the point's field +beyond that number is set to zero. + +.. _`Matlab struct`: https://www.mathworks.com/help/matlab/ref/struct.html + +.. note:: + + The Matlab reader requires the Mat-File API from MathWorks, and it must be + explicitly enabled at compile time with the ``BUILD_PLUGIN_MATLAB=ON`` + variable + +Example +------- + +.. code-block:: json + + { + "pipeline":[ + { + "type":"readers.matlab", + "struct":"PDAL", + "filename":"autzen.mat" + }, + { + "type":"writers.las", + "filename":"output.las" + } + ] + } + +Options +------- + +filename + Output file name [REQUIRED] + +struct + Array structure name to read [OPTIONAL, defaults ``PDAL``] diff --git a/pdal/StageFactory.cpp b/pdal/StageFactory.cpp index 051835655c..cac9c9747e 100644 --- a/pdal/StageFactory.cpp +++ b/pdal/StageFactory.cpp @@ -130,6 +130,7 @@ StringList StageFactory::extensions(const std::string& driver) { "readers.rxp", { "rxp" } }, { "readers.sbet", { "sbet" } }, { "readers.sqlite", { "sqlite" } }, + { "readers.matlab", { "mat" } }, { "readers.mrsid", { "sid" } }, { "readers.tindex", { "tindex" } }, { "readers.text", { "csv", "txt" } }, @@ -163,6 +164,7 @@ std::string StageFactory::inferReaderDriver(const std::string& filename) { "icebridge", "readers.icebridge" }, { "las", "readers.las" }, { "laz", "readers.las" }, + { "mat", "readers.matlab" }, { "nitf", "readers.nitf" }, { "nsf", "readers.nitf" }, { "ntf", "readers.nitf" }, diff --git a/plugins/matlab/CMakeLists.txt b/plugins/matlab/CMakeLists.txt index ef2ca13fcc..644a3b996a 100644 --- a/plugins/matlab/CMakeLists.txt +++ b/plugins/matlab/CMakeLists.txt @@ -2,22 +2,16 @@ include(${PDAL_CMAKE_DIR}/matlab.cmake) -set(MLANG ./filters/Environment.cpp - ./filters/Script.cpp) - -PDAL_ADD_PLUGIN(libname writer matlab +PDAL_ADD_PLUGIN(libname_writer writer matlab FILES ./filters/Script.cpp io/MatlabWriter.cpp LINK_WITH ${MATLAB_MX_LIBRARY} ${MATLAB_MAT_LIBRARY} ) -target_include_directories(${libname} PRIVATE +target_include_directories(${libname_writer} PRIVATE ${MATLAB_INCLUDE_DIR}) -target_compile_definitions(${libname} PRIVATE -DHAVE_MATLAB=1) -target_include_directories(${libname} PRIVATE ${MATLAB_INCLUDE_DIR}) - PDAL_ADD_PLUGIN(libname_reader reader matlab FILES ./filters/Script.cpp @@ -28,11 +22,6 @@ PDAL_ADD_PLUGIN(libname_reader reader matlab target_include_directories(${libname_reader} PRIVATE ${MATLAB_INCLUDE_DIR}) -target_compile_definitions(${libname_reader} PRIVATE -DHAVE_MATLAB=1) -target_include_directories(${libname_reader} PRIVATE ${MATLAB_INCLUDE_DIR}) - - - PDAL_ADD_PLUGIN(matlab_libname filter matlab FILES @@ -46,17 +35,27 @@ target_include_directories(${matlab_libname} PRIVATE ${MATLAB_INCLUDE_DIR} ) if (WITH_TESTS) - PDAL_ADD_TEST(matlabtest + PDAL_ADD_TEST(pdal_io_matlab_writer_test FILES test/MatlabWriterTest.cpp LINK_WITH - ${MATLAB_MX_LIBRARY} ${MATLAB_MAT_LIBRARY} ${libname} + ${MATLAB_MX_LIBRARY} ${MATLAB_MAT_LIBRARY} ${libname_writer} ) - target_include_directories(matlabtest PRIVATE + target_include_directories(pdal_io_matlab_writer_test PRIVATE ${PDAL_IO_DIR} ${MATLAB_INCLUDE_DIR}) + PDAL_ADD_TEST(pdal_io_matlab_reader_test + FILES + test/MatlabReaderTest.cpp + LINK_WITH + ${MATLAB_MX_LIBRARY} ${MATLAB_MAT_LIBRARY} ${libname_reader} + ) + target_include_directories(pdal_io_matlab_reader_test PRIVATE + ${PDAL_IO_DIR} + ${MATLAB_INCLUDE_DIR}) + PDAL_ADD_TEST(pdal_filters_matlab_test FILES ./filters/Script.cpp diff --git a/plugins/matlab/filters/Script.cpp b/plugins/matlab/filters/Script.cpp index 74d731bb83..4e022ff60c 100644 --- a/plugins/matlab/filters/Script.cpp +++ b/plugins/matlab/filters/Script.cpp @@ -71,6 +71,36 @@ std::string Script::getLogicalMask(mxArray* array, LogPtr log) return output; } +PointLayoutPtr Script::getStructLayout(mxArray* array, LogPtr log) +{ + + PointLayoutPtr layout = PointLayoutPtr(new PointLayout()); + std::vector arrays; + + mxClassID ml_id = mxGetClassID(array); + if (ml_id != mxSTRUCT_CLASS) + throw pdal::pdal_error("Selected array must be a Matlab struct array!"); + + + int numFields = mxGetNumberOfFields(array); + + if (!numFields) + throw pdal::pdal_error("Selected struct array must have fields!"); + + for (int i=0; i < numFields; ++i) + { + const char* fieldName = mxGetFieldNameByNumber(array, i); + mxArray* f = mxGetFieldByNumber(array, 0, i); + mxClassID mt = mxGetClassID(f); + Dimension::Type pt = Script::getPDALDataType(mt); + layout->registerOrAssignDim(fieldName, pt); + } + + layout->finalize(); + return layout; +} + + void Script::getMatlabStruct(mxArray* array, PointViewPtr view, const Dimension::IdList& indims, LogPtr log) { std::vector arrays; diff --git a/plugins/matlab/filters/Script.hpp b/plugins/matlab/filters/Script.hpp index 4e24809a15..a671c14a52 100644 --- a/plugins/matlab/filters/Script.hpp +++ b/plugins/matlab/filters/Script.hpp @@ -58,6 +58,7 @@ class PDAL_DLL Script static mxArray* setMatlabStruct(PointViewPtr view, const Dimension::IdList& dims, LogPtr log); static void getMatlabStruct(mxArray* array, PointViewPtr view, const Dimension::IdList& dims, LogPtr log); + static PointLayoutPtr getStructLayout(mxArray* array, LogPtr log); static std::string getLogicalMask(mxArray* array, LogPtr log); std::string m_source; diff --git a/plugins/matlab/io/MatlabReader.cpp b/plugins/matlab/io/MatlabReader.cpp index 278382baf8..faf28346b1 100644 --- a/plugins/matlab/io/MatlabReader.cpp +++ b/plugins/matlab/io/MatlabReader.cpp @@ -48,76 +48,79 @@ static PluginInfo const s_info = PluginInfo( "Matlab Reader", "http://pdal.io/stages/readers.matlab.html" ); -CREATE_STATIC_PLUGIN(1, 0, MatlabReader, Reader, s_info) - +CREATE_SHARED_PLUGIN(1, 0, MatlabReader, Reader, s_info) std::string MatlabReader::getName() const { return s_info.name; } void MatlabReader::initialize(PointTableRef table) { -// m_istream = Utils::openFile(m_filename); -// if (!m_istream) -// throwError("Unable to open text file '" + m_filename + "'."); -// -// std::string buf; -// std::getline(*m_istream, buf); -// -// auto isspecial = [](char c) -// { return (!std::isalnum(c) && c != ' '); }; -// -// // If the separator wasn't provided on the command line extract it -// // from the header line. -// if (m_separator == ' ') -// { -// // Scan string for some character not a number, space or letter. -// for (size_t i = 0; i < buf.size(); ++i) -// if (isspecial(buf[i])) -// { -// m_separator = buf[i]; -// break; -// } -// } -// -// if (m_separator != ' ') -// m_dimNames = Utils::split(buf, m_separator); -// else -// m_dimNames = Utils::split2(buf, m_separator); -// Utils::closeFile(m_istream); + m_matfile = matOpen(m_filename.c_str(), "r"); + if (!m_matfile) + throwError("Could not open file '" + m_filename + "' for reading."); + + m_pointIndex = 0; + m_numElements = 0; + m_numFields = 0; } void MatlabReader::addArgs(ProgramArgs& args) { -// args.add("separator", "Separator character that overrides special " -// "character in header line", m_separator, ' '); + args.add("struct", "Name of struct to read from file", m_structName, "PDAL"); } void MatlabReader::addDimensions(PointLayoutPtr layout) { - m_dims.clear(); -// for (auto name : m_dimNames) -// { -// Utils::trim(name); -// Dimension::Id id = layout->registerOrAssignDim(name, -// Dimension::Type::Double); -// if (Utils::contains(m_dims, id) && id != pdal::Dimension::Id::Unknown) -// throwError("Duplicate dimension '" + name + -// "' detected in input file '" + m_filename + "'."); -// m_dims.push_back(id); -// } -} + log()->get(LogLevel::Debug) << "Opening file" << + " '" << m_filename << "'." << std::endl; + m_structArray = matGetVariable(m_matfile, m_structName.c_str()); + if (!m_structArray) + { + std::ostringstream oss; + oss << "Array struct with name '" << m_structName << "' not found "; + oss << "in file '" << m_filename << "'"; + throwError(oss.str()); + } + + // For now we read all of the fields in the given + // array structure + m_numFields = mxGetNumberOfFields(m_structArray); + if (!m_numFields) + throw pdal::pdal_error("Selected struct array must have fields!"); + + // Fetch the first array and determine number of elements + // it has. We're only going to read that many elements no + // matter what + mxArray* f = mxGetFieldByNumber(m_structArray, 0, 0); + if (!f) + { + throwError("Unable to fetch first array in array struct to determine number of elements!"); + } + m_numElements = mxGetNumberOfElements(f); + + // Get the dimensions and their types from the array struct + PointLayoutPtr matlabLayout = mlang::Script::getStructLayout(m_structArray, log()); + const Dimension::IdList& dims = matlabLayout->dims(); + + int nDimensionNumber(0); + for(auto d: dims) + { + std::string dimName = matlabLayout->dimName(d); + const Dimension::Detail* detail = matlabLayout->dimDetail(d); + Dimension::Id id = detail->id(); + Dimension::Type t = detail->type(); + layout->registerDim(id, t); + + // Keep a map of the Matlab dimension number to + // both the PDAL type and PDAL id + std::pair pd = std::make_pair(nDimensionNumber, (int)id); + m_dimensionIdMap.insert(pd); + std::pair pt = std::make_pair(nDimensionNumber, (int)t); + m_dimensionTypeMap.insert(pt); + nDimensionNumber++; + } -void MatlabReader::ready(PointTableRef table) -{ -// m_istream = Utils::openFile(m_filename); -// if (!m_istream) -// throwError("Unable to open text file '" + m_filename + "'."); -// -// // Skip header line. -// std::string buf; -// std::getline(*m_istream, buf); -// m_line = 1; } @@ -140,26 +143,44 @@ point_count_t MatlabReader::read(PointViewPtr view, point_count_t numPts) bool MatlabReader::processOne(PointRef& point) { - double d; -// for (size_t i = 0; i < m_fields.size(); ++i) -// { -// if (!Utils::fromString(m_fields[i], d)) -// { -// log()->get(LogLevel::Error) << "Can't convert " -// "field '" << m_fields[i] << "' to numeric value on line " << -// m_line << " in '" << m_filename << "'. Setting to 0." << -// std::endl; -// d = 0; -// } -// point.setField(m_dims[i], d); -// } + // We read them all + if (m_pointIndex == m_numElements) + return false; + + for (int i=0; i < m_numFields; ++i) + { + Dimension::Id d = (Dimension::Id) m_dimensionIdMap[i]; + Dimension::Type t = (Dimension::Type) m_dimensionTypeMap[i]; + + mxArray* f = mxGetFieldByNumber(m_structArray, 0, i); + if (!f) + { + std::ostringstream oss; + oss << "Unable to fetch array for point " << m_pointIndex; + throwError(oss.str()); + } + PointId numElements = (PointId) mxGetNumberOfElements(f); + if (numElements >= m_pointIndex ) + { + size_t size = mxGetElementSize(f); + char* p = (char*)mxGetData(f) + size; + point.setField(d, t, (void*)p); + } + + + } + + m_pointIndex++; + return true; } - void MatlabReader::done(PointTableRef table) { + matClose(m_matfile); + getMetadata().addList("filename", m_filename); + getMetadata().add("struct", m_structName); } diff --git a/plugins/matlab/io/MatlabReader.hpp b/plugins/matlab/io/MatlabReader.hpp index 457edf6a05..6a9be40f77 100644 --- a/plugins/matlab/io/MatlabReader.hpp +++ b/plugins/matlab/io/MatlabReader.hpp @@ -38,6 +38,8 @@ #include #include +#include "../filters/Script.hpp" + #include @@ -80,13 +82,6 @@ class PDAL_DLL MatlabReader : public Reader */ virtual void addDimensions(PointLayoutPtr layout); - /** - Reopen the file in preparation for reading. - - \param table Point table to make ready. - */ - virtual void ready(PointTableRef table); - /** Read up to numPts points into the \ref view. @@ -111,18 +106,20 @@ class PDAL_DLL MatlabReader : public Reader */ virtual bool processOne(PointRef& point); - bool fillFields(); - private: - std::string m_filename; + std::string m_structName; MATFile * m_matfile; - PointLayout m_layout; + int m_numFields; + size_t m_numElements; + mxArray* m_structArray; + + std::map m_dimensionIdMap; + std::map m_dimensionTypeMap; + PointId m_pointIndex; - Dimension::IdList m_dims; - StringList m_fields; }; } // namespace pdal diff --git a/plugins/matlab/io/MatlabWriter.cpp b/plugins/matlab/io/MatlabWriter.cpp index 056c589a04..eb0135badd 100644 --- a/plugins/matlab/io/MatlabWriter.cpp +++ b/plugins/matlab/io/MatlabWriter.cpp @@ -56,6 +56,7 @@ void MatlabWriter::addArgs(ProgramArgs& args) { args.add("filename", "Output filename", m_filename).setPositional(); args.add("output_dims", "Output dimensions", m_outputDims); + args.add("struct", "Matlab struct name", m_structName, "PDAL"); } @@ -92,7 +93,7 @@ void MatlabWriter::write(const PointViewPtr view) { mxArray* data = mlang::Script::setMatlabStruct(view, m_dims, log()); - if (matPutVariable(m_matfile, "PDAL", data)) + if (matPutVariable(m_matfile, m_structName.c_str(), data)) throwError("Could not write points to file '" + m_filename + "'."); mxDestroyArray(data); diff --git a/plugins/matlab/io/MatlabWriter.hpp b/plugins/matlab/io/MatlabWriter.hpp index dc1d91aecc..1867740ff9 100644 --- a/plugins/matlab/io/MatlabWriter.hpp +++ b/plugins/matlab/io/MatlabWriter.hpp @@ -63,6 +63,7 @@ class PDAL_DLL MatlabWriter : public Writer virtual void done(PointTableRef table); std::string m_filename; + std::string m_structName; StringList m_outputDims; ///< List of dimensions to write // Can't use unique_ptr b/c MATFile is an incomplete type. MATFile * m_matfile; diff --git a/plugins/matlab/test/MatlabReaderTest.cpp b/plugins/matlab/test/MatlabReaderTest.cpp new file mode 100644 index 0000000000..ad198a5c81 --- /dev/null +++ b/plugins/matlab/test/MatlabReaderTest.cpp @@ -0,0 +1,59 @@ +/****************************************************************************** + * Copyright (c) 2017, Hobu Inc. (info@hobu.co) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Hobu, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + ****************************************************************************/ + +#include + +#include "Support.hpp" + +#include +#include "../io/MatlabReader.hpp" +#include + +using namespace pdal; + +TEST(MatlabReaderTest, t1) +{ + MatlabReader t; + Options mlabOptions; + mlabOptions.add("filename", Support::datapath("matlab/autzen.mat")); + t.setOptions(mlabOptions); + + PointTable tt; + t.prepare(tt); + PointViewSet ts = t.execute(tt); + EXPECT_EQ(ts.size(), 1U); + PointViewPtr tv = *ts.begin(); + EXPECT_EQ(tv->size(), 1065U); +} + diff --git a/test/data/matlab/autzen.mat b/test/data/matlab/autzen.mat new file mode 100644 index 0000000000000000000000000000000000000000..59194d6d787e82cf90ecd142ef615888e3a35f02 GIT binary patch literal 26629 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 Z$UL<5iqbA$TRx;u=i