diff --git a/examples/ex_hpiFit/main.cpp b/examples/ex_hpiFit/main.cpp index 677d126cf82..8371e8d6ecf 100644 --- a/examples/ex_hpiFit/main.cpp +++ b/examples/ex_hpiFit/main.cpp @@ -272,6 +272,6 @@ int main(int argc, char *argv[]) << "Average Duration:" << matPosition.col(9).mean() << "ms"; if(bSave) { - IOUtils::write_eigen_matrix(matPosition, QCoreApplication::applicationDirPath() + "/MNE-sample-data/" + sNameOut); + IOUtils::write_eigen_matrix(matPosition, QString(QCoreApplication::applicationDirPath() + "/MNE-sample-data/" + sNameOut)); } } diff --git a/examples/ex_read_fwd/main.cpp b/examples/ex_read_fwd/main.cpp index 6f5546814f3..41677a6a74f 100644 --- a/examples/ex_read_fwd/main.cpp +++ b/examples/ex_read_fwd/main.cpp @@ -124,7 +124,7 @@ int main(int argc, char *argv[]) qDebug() << "col_names:"; qDebug() << t_clusteredFwd.sol->col_names; qDebug() << "write fwd data to ./test_fwd.txt ..."; - IOUtils::write_eigen_matrix(t_clusteredFwd.sol->data,"./test_fwd.txt"); + IOUtils::write_eigen_matrix(t_clusteredFwd.sol->data, QString("./test_fwd.txt")); qDebug() << "[done]"; return app.exec(); diff --git a/examples/ex_spectral/main.cpp b/examples/ex_spectral/main.cpp index b3cd6a26e35..ba05663ca71 100644 --- a/examples/ex_spectral/main.cpp +++ b/examples/ex_spectral/main.cpp @@ -97,7 +97,7 @@ int main(int argc, char *argv[]) } //Generate hanning window - QPair tapers = Spectral::generateTapers(iNSamples, "hanning"); + QPair tapers = Spectral::generateTapers(iNSamples, QString("hanning")); MatrixXd matTaps = tapers.first; VectorXd vecTapWeights = tapers.second; diff --git a/libraries/utils/file.cpp b/libraries/utils/file.cpp index 13fe8d4dba9..2a3feb2c07f 100644 --- a/libraries/utils/file.cpp +++ b/libraries/utils/file.cpp @@ -39,11 +39,7 @@ #include "file.h" #include -#if __cplusplus >= 201703L -#include -#else #include -#endif //============================================================================================================= // USED NAMESPACES @@ -57,12 +53,8 @@ using namespace UTILSLIB; bool File::exists(const char* filePath) { -#if __cplusplus >= 201703L - return std::filesystem::exists(filePath); -#else std::ifstream infile(filePath); return infile.good(); -#endif } //============================================================================================================= @@ -79,11 +71,6 @@ bool File::copy(const char* sourcePath, const char* destPath) if (!exists(sourcePath) || exists(destPath)){ return false; } - -#if __cplusplus >= 201703L - std::filesystem::copy(sourcePath, destPath); - return exists(destPath); -#else std::ifstream source(sourcePath, std::ios::binary); std::ofstream destination(destPath, std::ios::binary); @@ -92,7 +79,6 @@ bool File::copy(const char* sourcePath, const char* destPath) } else { return false; } -#endif } //============================================================================================================= @@ -109,13 +95,7 @@ bool File::rename(const char* sourcePath, const char* destPath) if (!exists(sourcePath) || exists(destPath)){ return false; } - -#if __cplusplus >= 201703L - std::filesystem::rename(sourcePath, destPath); - return (!exists(sourcePath) && exists(destPath)); -#else return !std::rename(sourcePath, destPath); //std::rename returns 0 upon success -#endif } //============================================================================================================= @@ -133,12 +113,7 @@ bool File::remove(const char* filePath) return false; } -#if __cplusplus >= 201703L - std::filesystem::remove(filePath); - return !exists(filePath); -#else return !std::remove(filePath); //std::remove returns 0 upon success -#endif } //============================================================================================================= diff --git a/libraries/utils/generics/observerpattern.cpp b/libraries/utils/generics/observerpattern.cpp index cf5c7ece900..0944fe536da 100644 --- a/libraries/utils/generics/observerpattern.cpp +++ b/libraries/utils/generics/observerpattern.cpp @@ -73,6 +73,9 @@ void Subject::notify() t_Observers::const_iterator it = m_Observers.begin(); for( ; it != m_Observers.end(); ++it) (*it)->update(this); +// for(auto observer : m_Observers){ +// observer->update(this); +// } } } diff --git a/libraries/utils/generics/observerpattern.h b/libraries/utils/generics/observerpattern.h index 9bd1af73fa8..5c4319912f1 100644 --- a/libraries/utils/generics/observerpattern.h +++ b/libraries/utils/generics/observerpattern.h @@ -41,6 +41,7 @@ //============================================================================================================= #include "../utils_global.h" +#include //============================================================================================================= // QT INCLUDES @@ -147,6 +148,14 @@ class UTILSSHARED_EXPORT Subject */ inline t_Observers& observers(); +// //========================================================================================================= +// /** +// * Returns attached observers. + +// * @return attached observers. +// */ +// inline std::set& observers(); + //========================================================================================================= /** * Returns number of attached observers. @@ -165,6 +174,7 @@ class UTILSSHARED_EXPORT Subject private: t_Observers m_Observers; /**< Holds the attached observers.*/ +// std::set m_Observers; /**< Holds the attached observers.*/ }; //============================================================================================================= @@ -176,6 +186,11 @@ inline Subject::t_Observers& Subject::observers() return m_Observers; } +//inline std::set& Subject::observers() +//{ +// return m_Observers; +//} + } // Namespace #endif // OBSERVERPATTERN_H diff --git a/libraries/utils/ioutils.cpp b/libraries/utils/ioutils.cpp index cc387101f05..dd1d4704350 100644 --- a/libraries/utils/ioutils.cpp +++ b/libraries/utils/ioutils.cpp @@ -2,13 +2,14 @@ /** * @file ioutils.cpp * @author Lorenz Esch ; - * Christoph Dinh + * Christoph Dinh ; + * Gabriel Motta * @since 0.1.0 * @date March, 2013 * * @section LICENSE * - * Copyright (C) 2013, Lorenz Esch, Christoph Dinh. All rights reserved. + * Copyright (C) 2013, Lorenz Esch, Christoph Dinh, Gabriel Motta. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that * the following conditions are met: @@ -38,6 +39,8 @@ //============================================================================================================= #include "ioutils.h" +#include +#include //============================================================================================================= // QT INCLUDES @@ -73,6 +76,17 @@ qint32 IOUtils::fread3(QDataStream &p_qStream) //============================================================================================================= +qint32 IOUtils::fread3(std::iostream& stream) +{ + char* bytes = new char[3]; + stream.read(bytes, 3); + qint32 int3 = (((unsigned char) bytes[0]) << 16) + (((unsigned char) bytes[1]) << 8) + ((unsigned char) bytes[2]); + delete[] bytes; + return int3; +} + +//============================================================================================================= + VectorXi IOUtils::fread3_many(QDataStream &p_qStream, qint32 count) { VectorXi res(count); @@ -83,6 +97,18 @@ VectorXi IOUtils::fread3_many(QDataStream &p_qStream, qint32 count) return res; } +//============================================================================================================= + +VectorXi IOUtils::fread3_many(std::iostream& stream, qint32 count) +{ + VectorXi res(count); + + for(qint32 i = 0; i < count; ++i) + res[i] = IOUtils::fread3(stream); + + return res; +} + //============================================================================================================= //fiff_combat qint16 IOUtils::swap_short(qint16 source) @@ -253,6 +279,20 @@ QStringList IOUtils::get_new_chnames_conventions(const QStringList& chNames) //============================================================================================================= +std::vector IOUtils::get_new_chnames_conventions(const std::vector& chNames) +{ + std::vector result; + + for(auto channelName : chNames){ + std::remove(channelName.begin(), channelName.end(), ' '); + result.push_back(std::move(channelName)); + } + + return result; +} + +//============================================================================================================= + QStringList IOUtils::get_old_chnames_conventions(const QStringList& chNames) { QStringList result, xList; @@ -276,6 +316,20 @@ QStringList IOUtils::get_old_chnames_conventions(const QStringList& chNames) //============================================================================================================= +std::vector IOUtils::get_old_chnames_conventions(const std::vector& chNames) +{ + std::vector result; + + for(auto channelName : chNames){ + std::regex_replace(channelName, std::regex("[0-9]{1,100}"), " $&"); + result.push_back(std::move(channelName)); + } + + return result; +} + +//============================================================================================================= + bool IOUtils::check_matching_chnames_conventions(const QStringList& chNamesA, const QStringList& chNamesB, bool bCheckForNewNamingConvention) { bool bMatching = false; @@ -322,3 +376,39 @@ bool IOUtils::check_matching_chnames_conventions(const QStringList& chNamesA, co return bMatching; } + +//============================================================================================================= + +bool IOUtils::check_matching_chnames_conventions(const std::vector& chNamesA, const std::vector& chNamesB, bool bCheckForNewNamingConvention) +{ + if(chNamesA.empty()){ + qWarning("Warning in IOUtils::check_matching_chnames_conventions - chNamesA list is empty. Nothing to compare"); + } + if(chNamesB.empty()){ + qWarning("Warning in IOUtils::check_matching_chnames_conventions - chNamesB list is empty. Nothing to compare"); + } + + bool bMatching = false; + + for(size_t i = 0 ; i < chNamesA.size(); ++i){ + if (std::find(chNamesB.begin(), chNamesB.end(), chNamesA.at(i)) != chNamesB.end()){ + bMatching = true; + } else if(bCheckForNewNamingConvention){ + std::string replaceStringNewConv{chNamesA.at(i)}; + std::remove(replaceStringNewConv.begin(), replaceStringNewConv.end(), ' '); + + if(std::find(chNamesB.begin(), chNamesB.end(), replaceStringNewConv) != chNamesB.end()){ + bMatching = true; + } else { + std::string replaceStringOldConv{chNamesA.at(i)}; + std::regex_replace(replaceStringOldConv, std::regex("[0-9]{1,100}"), " $&"); + if(std::find(chNamesB.begin(), chNamesB.end(), replaceStringNewConv) != chNamesB.end() || std::find(chNamesB.begin(), chNamesB.end(), replaceStringOldConv) != chNamesB.end()){ + bMatching = true; + } else { + bMatching = false; + } + } + } + } + return bMatching; +} diff --git a/libraries/utils/ioutils.h b/libraries/utils/ioutils.h index b82e146a867..8eb5a3a4c78 100644 --- a/libraries/utils/ioutils.h +++ b/libraries/utils/ioutils.h @@ -2,13 +2,14 @@ /** * @file ioutils.h * @author Lorenz Esch ; - * Christoph Dinh + * Christoph Dinh ; + * Gabriel Motta * @since 0.1.0 * @date March, 2013 * * @section LICENSE * - * Copyright (C) 2013, Lorenz Esch, Christoph Dinh. All rights reserved. + * Copyright (C) 2013, Lorenz Esch, Christoph Dinh, Gabriel Motta. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that * the following conditions are met: @@ -40,6 +41,11 @@ //============================================================================================================= #include "utils_global.h" +#include +#include +#include +#include +#include //============================================================================================================= // QT INCLUDES @@ -108,6 +114,18 @@ class UTILSSHARED_EXPORT IOUtils */ static qint32 fread3(QDataStream &p_qStream); + //========================================================================================================= + /** + * mne_fread3(fid) + * + * Reads a 3-byte integer out of a stream + * + * @param[in] stream Stream to read from. + * + * @return the read 3-byte integer. + */ + static qint32 fread3(std::iostream& stream); + //========================================================================================================= /** * fread3_many(fid,count) @@ -121,6 +139,19 @@ class UTILSSHARED_EXPORT IOUtils */ static Eigen::VectorXi fread3_many(QDataStream &p_qStream, qint32 count); + //========================================================================================================= + /** + * fread3_many(fid,count) + * + * Reads a 3-byte integer out of a stream + * + * @param[in] stream Stream to read from. + * @param[in] count Number of elements to read. + * + * @return the read 3-byte integer. + */ + static Eigen::VectorXi fread3_many(std::iostream &stream, qint32 count); + //========================================================================================================= /** * swap short @@ -207,6 +238,20 @@ class UTILSSHARED_EXPORT IOUtils template static bool write_eigen_matrix(const Eigen::Matrix& in, const QString& sPath, const QString& sDescription = QString()); + //========================================================================================================= + /** + * Write Eigen Matrix to file + * + * @param[in] in input eigen value which is to be written to file. + * @param[in] path path and file name to write to. + */ + template + static bool write_eigen_matrix(const Eigen::Matrix& in, const std::string& sPath, const std::string& sDescription = std::string()); + template + static bool write_eigen_matrix(const Eigen::Matrix& in, const std::string& sPath, const std::string& sDescription = std::string()); + template + static bool write_eigen_matrix(const Eigen::Matrix& in, const std::string& sPath, const std::string& sDescription = std::string()); + //========================================================================================================= /** * Read Eigen Matrix from file @@ -221,6 +266,20 @@ class UTILSSHARED_EXPORT IOUtils template static bool read_eigen_matrix(Eigen::Matrix& out, const QString& path); + //========================================================================================================= + /** + * Read Eigen Matrix from file + * + * @param[out] out output eigen value. + * @param[in] path path and file name to read from. + */ + template + static bool read_eigen_matrix(Eigen::Matrix& out, const std::string& path); + template + static bool read_eigen_matrix(Eigen::Matrix& out, const std::string& path); + template + static bool read_eigen_matrix(Eigen::Matrix& out, const std::string& path); + //========================================================================================================= /** * Returns the new channel naming conventions (whitespcae between channel type and number) for the input list. @@ -231,6 +290,16 @@ class UTILSSHARED_EXPORT IOUtils */ static QStringList get_new_chnames_conventions(const QStringList& chNames); + //========================================================================================================= + /** + * Returns the new channel naming conventions (whitespcae between channel type and number) for the input list. + * + * @param[in] chNames The channel names. + * + * @return The new channel names. + */ + static std::vector get_new_chnames_conventions(const std::vector& chNames); + //========================================================================================================= /** * Returns the old channel naming conventions (whitespcae between channel type and number) for the input list. @@ -241,6 +310,16 @@ class UTILSSHARED_EXPORT IOUtils */ static QStringList get_old_chnames_conventions(const QStringList& chNames); + //========================================================================================================= + /** + * Returns the old channel naming conventions (whitespcae between channel type and number) for the input list. + * + * @param[in] chNames The channel names. + * + * @return The new channel names. + */ + static std::vector get_old_chnames_conventions(const std::vector& chNames); + //========================================================================================================= /** * Checks if all names from chNamesA are in chNamesB. If wanted each name in chNamesA is transformed to the old and new naming convention and checked if in chNamesB. @@ -252,6 +331,18 @@ class UTILSSHARED_EXPORT IOUtils * @return True if all names in chNamesA are present in chNamesB, false otherwise. */ static bool check_matching_chnames_conventions(const QStringList& chNamesA, const QStringList& chNamesB, bool bCheckForNewNamingConvention = false); + + //========================================================================================================= + /** + * Checks if all names from chNamesA are in chNamesB. If wanted each name in chNamesA is transformed to the old and new naming convention and checked if in chNamesB. + * + * @param[in] chNamesA The channel names. + * @param[in] chNamesB The channel names which is to be compared to. + * @param[in] bCheckForNewNamingConvention Whether to use old and new naming conventions while checking. + * + * @return True if all names in chNamesA are present in chNamesB, false otherwise. + */ + static bool check_matching_chnames_conventions(const std::vector& chNamesA, const std::vector& chNamesB, bool bCheckForNewNamingConvention = false); }; //============================================================================================================= @@ -307,6 +398,52 @@ bool IOUtils::write_eigen_matrix(const Eigen::Matrix +bool IOUtils::write_eigen_matrix(const Eigen::Matrix& in, const std::string& sPath, const std::string& sDescription) +{ + Eigen::Matrix matrixName(1,in.cols()); + matrixName.row(0)= in; + return IOUtils::write_eigen_matrix(matrixName, sPath, sDescription); +} + +//============================================================================================================= + +template +bool IOUtils::write_eigen_matrix(const Eigen::Matrix& in, const std::string& sPath, const std::string& sDescription) +{ + Eigen::Matrix matrixName(in.rows(),1); + matrixName.col(0)= in; + return IOUtils::write_eigen_matrix(matrixName, sPath, sDescription); +} + +//============================================================================================================= + +template +bool IOUtils::write_eigen_matrix(const Eigen::Matrix& in, const std::string& sPath, const std::string& sDescription) +{ + std::ofstream outputFile(sPath); + if(outputFile.is_open()) + { + if(!sDescription.empty()) { + outputFile<<"# Dimensions (rows x cols): "< bool IOUtils::read_eigen_matrix(Eigen::Matrix& out, const QString& path) { @@ -361,8 +498,8 @@ bool IOUtils::read_eigen_matrix(Eigen::Matrix Eigen::VectorXd x (fields.size()); - for (int j=0; j } int rows = help.size(); - int cols = rows<=0 ? 0 : help.at(0).rows(); + int cols = rows <= 0 ? 0 : help.at(0).rows(); + + out.resize(rows, cols); + + for (int i=0; i < help.length(); i++) { + out.row(i) = help[i].transpose(); + } + } else { + qWarning()<<"IOUtils::read_eigen_matrix - Could not read Eigen element from file! Path does not exist!"; + return false; + } + + return true; +} + +//============================================================================================================= + +template +bool IOUtils::read_eigen_matrix(Eigen::Matrix& out, const std::string& path) +{ + Eigen::Matrix matrixName; + bool bStatus = IOUtils::read_eigen_matrix(matrixName, path); + + if(matrixName.rows() > 0) + { + out = matrixName.row(0); + } + + return bStatus; +} + +//============================================================================================================= + +template +bool IOUtils::read_eigen_matrix(Eigen::Matrix& out, const std::string& path) +{ + Eigen::Matrix matrixName; + bool bStatus = IOUtils::read_eigen_matrix(matrixName, path); + + if(matrixName.cols() > 0) + { + out = matrixName.col(0); + } + + return bStatus; +} + +//============================================================================================================= + +template +bool IOUtils::read_eigen_matrix(Eigen::Matrix& out, const std::string& path) +{ + std::ifstream inputFile(path); + + if(inputFile.is_open()) { + //Start reading from file + std::vector help; + + std::string line; + + while(std::getline(inputFile, line)){ + if(line.find('#') == std::string::npos){ + std::vector elements; + std::stringstream stream{line}; + std::string element; + + stream >> std::ws; + while(stream >> element){ + elements.push_back(std::stod(element)); + stream >> std::ws; + } + + Eigen::VectorXd x (elements.size()); + + for(size_t i = 0; i < elements.size(); ++i){ + x(i) = elements.at(i); + } + + help.push_back(std::move(x)); + } + } + + int rows = help.size(); + int cols = rows <= 0 ? 0 : help.at(0).rows(); out.resize(rows, cols); - for (int i=0; i; - * Christoph Dinh + * Christoph Dinh ; + * Gabriel Motta * @since 0.1.0 * @date July, 2012 * * @section LICENSE * - * Copyright (C) 2012, Lorenz Esch, Christoph Dinh. All rights reserved. + * Copyright (C) 2012, Lorenz Esch, Christoph Dinh, Gabriel Motta. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that * the following conditions are met: @@ -68,10 +69,10 @@ KMeans::KMeans(QString distance, QString emptyact, bool online, qint32 maxit) -: m_sDistance(distance) -, m_sStart(start) +: m_sDistance(distance.toStdString()) +, m_sStart(start.toStdString()) , m_iReps(replicates) -, m_sEmptyact(emptyact) +, m_sEmptyact(emptyact.toStdString()) , m_iMaxit(maxit) , m_bOnline(online) , emptyErrCnt(0) @@ -89,6 +90,33 @@ KMeans::KMeans(QString distance, //============================================================================================================= +//KMeans::KMeans(std::string distance, +// std::string start, +// qint32 replicates, +// std::string emptyact, +// bool online, +// qint32 maxit) +//: m_sDistance(distance) +//, m_sStart(start) +//, m_iReps(replicates) +//, m_sEmptyact(emptyact) +//, m_iMaxit(maxit) +//, m_bOnline(online) +//, emptyErrCnt(0) +//, iter(0) +//, k(0) +//, n(0) +//, p(0) +//, totsumD(0) +//, prevtotsumD(0) +//{ +// // Assume one replicate +// if (m_iReps < 1) +// m_iReps = 1; +//} + +//============================================================================================================= + bool KMeans::calculate(MatrixXd X, qint32 kClusters, VectorXi& idx, diff --git a/libraries/utils/kmeans.h b/libraries/utils/kmeans.h index 7a0b840ecfc..e5b49f4de3c 100644 --- a/libraries/utils/kmeans.h +++ b/libraries/utils/kmeans.h @@ -2,13 +2,14 @@ /** * @file kmeans.h * @author Lorenz Esch ; - * Christoph Dinh + * Christoph Dinh ; + * Gabriel Motta * @since 0.1.0 * @date July, 2012 * * @section LICENSE * - * Copyright (C) 2012, Lorenz Esch, Christoph Dinh. All rights reserved. + * Copyright (C) 2012, Lorenz Esch, Christoph Dinh, Gabriel Motta. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that * the following conditions are met: @@ -46,7 +47,7 @@ // QT INCLUDES //============================================================================================================= -#include +#include #include //============================================================================================================= @@ -96,6 +97,24 @@ class UTILSSHARED_EXPORT KMeans bool online = true, qint32 maxit = 100); +// //========================================================================================================= +// /** +// * Constructs a KMeans algorithm object. +// * +// * @param[in] distance (optional) K-Means distance measure: "sqeuclidean" (default), "cityblock" , "cosine", "correlation", "hamming". +// * @param[in] start (optional) Cluster initialization: "sample" (default), "uniform", "cluster". +// * @param[in] replicates (optional) Number of K-Means replicates, which are generated. Best is returned. +// * @param[in] emptyact (optional) What happens if a cluster wents empty: "error" (default), "drop", "singleton". +// * @param[in] online (optional) If centroids should be updated during iterations: true (default), false. +// * @param[in] maxit (optional) maximal number of iterations per replicate; 100 by default. +// */ +// explicit KMeans(std::string distance = std::string{"sqeuclidean"} , +// std::string start = std::string{"sample"}, +// qint32 replicates = 1, +// std::string emptyact = std::string{"error"}, +// bool online = true, +// qint32 maxit = 100); + //========================================================================================================= /** * Clusters input data X @@ -182,10 +201,10 @@ class UTILSSHARED_EXPORT KMeans */ double unifrnd(double a, double b); - QString m_sDistance; /**< Distance measurement to use: "sqeuclidean" (default), "cityblock" , "cosine", "correlation", "hamming". */ - QString m_sStart; /**< Initialization to use: "sample" (default), "uniform", "cluster". */ + std::string m_sDistance; /**< Distance measurement to use: "sqeuclidean" (default), "cityblock" , "cosine", "correlation", "hamming". */ + std::string m_sStart; /**< Initialization to use: "sample" (default), "uniform", "cluster". */ qint32 m_iReps; /**< Number of K-Means replicates, which should be generated. */ - QString m_sEmptyact; /**< What should be done if a cluster wents empty: "error" (default), "drop", "singleton". */ + std::string m_sEmptyact; /**< What should be done if a cluster wents empty: "error" (default), "drop", "singleton". */ qint32 m_iMaxit; /**< Maximal number of iterations per replicate. */ bool m_bOnline; /**< If online update should be performed. */ diff --git a/libraries/utils/layoutloader.cpp b/libraries/utils/layoutloader.cpp index 70e819cd813..b08c2f48c64 100644 --- a/libraries/utils/layoutloader.cpp +++ b/libraries/utils/layoutloader.cpp @@ -2,13 +2,14 @@ /** * @file layoutloader.cpp * @author Lorenz Esch ; - * Christoph Dinh + * Christoph Dinh ; + * Gabriel Motta * @since 0.1.0 * @date September, 2014 * * @section LICENSE * - * Copyright (C) 2014, Lorenz Esch, Christoph Dinh. All rights reserved. + * Copyright (C) 2014, Lorenz Esch, Christoph Dinh, Gabriel Motta. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that * the following conditions are met: @@ -38,6 +39,8 @@ //============================================================================================================= #include "layoutloader.h" +#include +#include //============================================================================================================= // QT INCLUDES @@ -56,13 +59,7 @@ using namespace UTILSLIB; //============================================================================================================= // DEFINE MEMBER METHODS -//============================================================================================================= - -LayoutLoader::LayoutLoader() -{ -} - -//============================================================================================================= +//=============================================================================================================s bool LayoutLoader::readAsaElcFile(const QString& path, QStringList &channelNames, @@ -153,6 +150,101 @@ bool LayoutLoader::readAsaElcFile(const QString& path, //============================================================================================================= +bool LayoutLoader::readAsaElcFile(const std::string &path, + std::vector &channelNames, + std::vector > &location3D, + std::vector > &location2D, + std::string &unit) +{ + + if(path.find(".elc") == std::string::npos){ + return false; + } + + std::ifstream inFile(path); + + if(!inFile.is_open()){ + qDebug()<<"Error opening elc file"; + return false; + } + + //Start reading from file + double numberElectrodes; + bool read2D = false; + + std::string line; + + while(std::getline(inFile, line)){ + if(line.find('#') == std::string::npos){ + std::vector elements; + std::stringstream stream{line}; + std::string element; + + stream >> std::ws; + while(stream >> element){ + elements.push_back(std::move(element)); + stream >> std::ws; + } + + //Read number of electrodes + if(line.find("NumberPositions") != std::string::npos) + numberElectrodes = std::stod(elements.at(1)); + + //Read the unit of the position values + if(line.find("UnitPosition") != std::string::npos) + unit = elements.at(1); + + //Read actual electrode positions + if(line.find("Positions2D") != std::string::npos) + read2D = true; + + if(line.find(':') != std::string::npos && !read2D) //Read 3D positions + { + channelNames.push_back(elements.at(0)); + std::vector posTemp; + + posTemp.push_back(std::stod(elements.at(elements.size()-3))); //x + posTemp.push_back(std::stod(elements.at(elements.size()-2))); //y + posTemp.push_back(std::stod(elements.at(elements.size()-1))); //z + + location3D.push_back(std::move(posTemp)); + } + + if(line.find(":") != std::string::npos && read2D) //Read 2D positions + { + std::vector posTemp; + posTemp.push_back(std::stod(elements.at(elements.size()-2))); //x + posTemp.push_back(std::stod(elements.at(elements.size()-1))); //y + location2D.push_back(std::move(posTemp)); + } + + //Read channel names + if(line.find("Labels") != std::string::npos) + { + std::getline(inFile, line); + std::stringstream channels{line}; + std::vector listOfNames; + + std::string channelName; + + channels >> std::ws; + while(channels >> channelName){ + listOfNames.push_back(std::move(channelName)); + channels >> std::ws; + } + + channelNames = std::move(listOfNames); + } + } + } + + Q_UNUSED(numberElectrodes); + + return true; +} + +//============================================================================================================= + bool LayoutLoader::readMNELoutFile(const QString &path, QMap &channelData) { //Open .elc file @@ -195,3 +287,48 @@ bool LayoutLoader::readMNELoutFile(const QString &path, QMap & return true; } + +//============================================================================================================= + +bool LayoutLoader::readMNELoutFile(const std::string &path, QMap &channelData) +{ + + if(path.find(".lout") == std::string::npos){ + return false; + } + + channelData.clear(); + std::ifstream inFile(path); + + if(!inFile.is_open()){ + qDebug()<<"Error opening mne lout file"; + return false; + } + + + std::string line; + + while(std::getline(inFile, line)){ + if(line.find('#') != std::string::npos){ + std::vector elements; + std::stringstream stream{line}; + std::string element; + + stream >> std::ws; + while(stream >> element){ + elements.push_back(std::move(element)); + stream >> std::ws; + } + + QPointF posTemp; + posTemp.setX(std::stod(elements.at(1))); //x + posTemp.setY(std::stod(elements.at(2))); //y + + //Create channel data map entry + std::string key{elements.at(elements.size() - 2) + " " + elements.at(elements.size() - 1)}; + channelData.insert(key, posTemp); + } + } + + return true; +} diff --git a/libraries/utils/layoutloader.h b/libraries/utils/layoutloader.h index b4f9e605233..279db9edb61 100644 --- a/libraries/utils/layoutloader.h +++ b/libraries/utils/layoutloader.h @@ -3,12 +3,13 @@ * @file layoutloader.h * @author Lorenz Esch ; * Christoph Dinh + * Gabriel Motta * @since 0.1.0 * @date September, 2014 * * @section LICENSE * - * Copyright (C) 2014, Lorenz Esch, Christoph Dinh. All rights reserved. + * Copyright (C) 2014, Lorenz Esch, Christoph Dinh, Gabriel Motta. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that * the following conditions are met: @@ -42,6 +43,9 @@ #include "utils_global.h" +#include +#include + //============================================================================================================= // QT INCLUDES //============================================================================================================= @@ -81,12 +85,6 @@ class UTILSSHARED_EXPORT LayoutLoader typedef QSharedPointer SPtr; /**< Shared pointer type for LayoutLoader. */ typedef QSharedPointer ConstSPtr; /**< Const shared pointer type for LayoutLoader. */ - //========================================================================================================= - /** - * Constructs a LayoutLoader object. - */ - LayoutLoader(); - //========================================================================================================= /** * Reads the specified ANT elc-layout file. @@ -101,6 +99,20 @@ class UTILSSHARED_EXPORT LayoutLoader QList > &location2D, QString &unit); + //========================================================================================================= + /** + * Reads the specified ANT elc-layout file. + * @param[in] path holds the file path of the elc file which is to be read. + * @param[in] location3D holds the vector to which the read 3D positions are stored. + * @param[in] location2D holds the vector to which the read 2D positions are stored. + * @return true if reading was successful, false otherwise. + */ + static bool readAsaElcFile(const std::string &path, + std::vector &channelNames, + std::vector > &location3D, + std::vector > &location2D, + std::string &unit); + //========================================================================================================= /** * Reads the specified MNE .lout file. @@ -111,6 +123,16 @@ class UTILSSHARED_EXPORT LayoutLoader static bool readMNELoutFile(const QString &path, QMap &channelData); + //========================================================================================================= + /** + * Reads the specified MNE .lout file. + * @param[in] path holds the file path of the lout file which is to be read. + * @param[in] channel data holds the x,y and channel number for every channel. The map keys are the channel names (i.e. 'MEG 0113'). + * @return bool true if reading was successful, false otherwise. + */ + static bool readMNELoutFile(const std::string &path, + QMap &channelData); + private: }; } // NAMESPACE diff --git a/libraries/utils/layoutmaker.cpp b/libraries/utils/layoutmaker.cpp index 002a2e06d92..f8471b80719 100644 --- a/libraries/utils/layoutmaker.cpp +++ b/libraries/utils/layoutmaker.cpp @@ -2,13 +2,14 @@ /** * @file layoutmaker.cpp * @author Lorenz Esch ; - * Christoph Dinh + * Christoph Dinh ; + * Gabriel Motta * @since 0.1.0 * @date September, 2014 * * @section LICENSE * - * Copyright (C) 2014, Lorenz Esch, Christoph Dinh. All rights reserved. + * Copyright (C) 2014, Lorenz Esch, Christoph Dinh, Gabriel Motta. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that * the following conditions are met: @@ -71,12 +72,6 @@ using namespace Eigen; // DEFINE MEMBER METHODS //============================================================================================================= -LayoutMaker::LayoutMaker() -{ -} - -//============================================================================================================= - bool LayoutMaker::makeLayout(const QList > &inputPoints, QList > &outputPoints, const QStringList &names, @@ -106,7 +101,7 @@ bool LayoutMaker::makeLayout(const QList > &inputPoints, VectorXf yy(nchan); if (nchan <= 0) { - std::cout << "No input points to lay out." << std::endl; + std::cout << "No input points to lay out.\n"; return false; } @@ -117,11 +112,11 @@ bool LayoutMaker::makeLayout(const QList > &inputPoints, rrs(k,2) = inputPoints.at(k)[2]; //z } - std::cout << "Channels found for layout: " << nchan << std::endl; + std::cout << "Channels found for layout: " << nchan << "\n"; //Fit to sphere if wanted by the user if (!do_fit) { - std::cout << "Using default origin:" << r0[0] << ", " << r0[1] << ", " << r0[2] << std::endl; + std::cout << "Using default origin:" << r0[0] << ", " << r0[1] << ", " << r0[2] << "\n"; } else { Sphere sphere = Sphere::fit_sphere_simplex(rrs, 0.05); @@ -129,8 +124,8 @@ bool LayoutMaker::makeLayout(const QList > &inputPoints, r0 = sphere.center(); rad = sphere.radius(); - std::cout << "best fitting sphere:" << std::endl; - std::cout << "torigin: " << r0[0] << ", " << r0[1] << ", " << r0[2] << std::endl << "; tradius: " << rad << std::endl; + std::cout << "best fitting sphere:\n"; + std::cout << "torigin: " << r0[0] << ", " << r0[1] << ", " << r0[2] << std::endl << "; tradius: " << rad << "\n"; } /* @@ -161,7 +156,7 @@ bool LayoutMaker::makeLayout(const QList > &inputPoints, } if(xmin == xmax || ymin == ymax) { - std::cout<<"Cannot make a layout. All positions are identical"< > &inputPoints, if(writeFile) { if (!outFile.open(QIODevice::WriteOnly)) { - std::cout << "Could not open output file!" << std::endl; + std::cout << "Could not open output file!\n"; return false; } @@ -213,7 +208,7 @@ bool LayoutMaker::makeLayout(const QList > &inputPoints, } if(writeFile) { - std::cout << "Success while wrtiting to output file." << std::endl; + std::cout << "Success while wrtiting to output file.\n"; outFile.close(); } @@ -223,6 +218,148 @@ bool LayoutMaker::makeLayout(const QList > &inputPoints, //============================================================================================================= +bool LayoutMaker::makeLayout(const std::vector > &inputPoints, + std::vector > &outputPoints, + const std::vector &names, + const std::string& outFilePath, + bool do_fit, + float prad, + float w, + float h, + bool writeFile, + bool mirrorXAxis, + bool mirrorYAxis) +{ + /* + * Automatically make a layout according to the + * channel locations in inputPoints + */ + VectorXf r0(3); + VectorXf rr(3); + float rad,th,phi; + + float xmin,xmax,ymin,ymax; + int nchan = inputPoints.size(); + + MatrixXf rrs(nchan,3); + VectorXf xx(nchan); + VectorXf yy(nchan); + + if (nchan <= 0) { + std::cout << "No input points to lay out.\n"; + return false; + } + + //Fill matrix with 3D points + for(int k = 0; k < nchan; k++) { + rrs(k,0) = inputPoints.at(k)[0]; //x + rrs(k,1) = inputPoints.at(k)[1]; //y + rrs(k,2) = inputPoints.at(k)[2]; //z + } + + std::cout << "Channels found for layout: " << nchan << "\n"; + + //Fit to sphere if wanted by the user + if (!do_fit) { + std::cout << "Using default origin:" << r0[0] << ", " << r0[1] << ", " << r0[2] << "\n"; + } + else { + Sphere sphere = Sphere::fit_sphere_simplex(rrs, 0.05); + + r0 = sphere.center(); + rad = sphere.radius(); + + std::cout << "best fitting sphere:\n"; + std::cout << "torigin: " << r0[0] << ", " << r0[1] << ", " << r0[2] << std::endl << "; tradius: " << rad << "\n"; + } + + /* + * Do the azimuthal equidistant projection + */ + for (int k = 0; k < nchan; k++) { + rr = r0 - static_cast(rrs.row(k)); + sphere_coord(rr[0],rr[1],rr[2],&rad,&th,&phi); + xx[k] = prad*(2.0*th/M_PI)*cos(phi); + yy[k] = prad*(2.0*th/M_PI)*sin(phi); + } + + /* + * Find suitable range of viewports + */ + xmin = xmax = xx[0]; + ymin = ymax = yy[0]; + + for(int k = 1; k < nchan; k++) { + if (xx[k] > xmax) + xmax = xx[k]; + else if (xx[k] < xmin) + xmin = xx[k]; + if (yy[k] > ymax) + ymax = yy[k]; + else if (yy[k] < ymin) + ymin = yy[k]; + } + + if(xmin == xmax || ymin == ymax) { + std::cout<<"Cannot make a layout. All positions are identical\n"; + return false; + } + + xmax = xmax + 0.6*w; + xmin = xmin - 0.6*w; + + ymax = ymax + 0.6*h; + ymin = ymin - 0.6*h; + + /* + * Compose the viewports + */ + std::vector point; + std::ofstream outFile; + + if(writeFile) { + outFile.open(outFilePath); + if (outFile.is_open()) { + std::cout << "Could not open output file!\n"; + return false; + } + outFile << "0.000000 0.000000 0.000000 0.000000" << std::endl; + } + + + for(int k = 0; k < nchan; k++) { + point.clear(); + + if(mirrorXAxis) + point.push_back(-(xx[k]-0.5*w)); + else + point.push_back(xx[k]-0.5*w); + + if(mirrorYAxis) + point.push_back(-(yy[k]-0.5*h)); + else + point.push_back(yy[k]-0.5*h); + + outputPoints.push_back(point); + + if(writeFile) { + if((k) < names.size()) { + outFile << k+1 << " " << point[0] << " " << point[1] << " " << w << " " << h << " " << names.at(k) << std::endl; + } else { + outFile << k+1 << " " << point[0] << " " << point[1] << " " << w << " " << h << std::endl; + } + } + } + + if(writeFile) { + std::cout << "Success while wrtiting to output file.\n"; + } + + return true; +} + +//============================================================================================================= + void LayoutMaker::sphere_coord (float x, float y, float z, diff --git a/libraries/utils/layoutmaker.h b/libraries/utils/layoutmaker.h index 93e06f10c0d..423316caf47 100644 --- a/libraries/utils/layoutmaker.h +++ b/libraries/utils/layoutmaker.h @@ -2,13 +2,14 @@ /** * @file layoutmaker.h * @author Lorenz Esch ; - * Christoph Dinh + * Christoph Dinh ; + * Gabriel Motta * @since 0.1.0 * @date September, 2014 * * @section LICENSE * - * Copyright (C) 2014, Lorenz Esch, Christoph Dinh. All rights reserved. + * Copyright (C) 2014, Lorenz Esch, Christoph Dinh, Gabriel Motta. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that * the following conditions are met: @@ -42,6 +43,10 @@ #include "utils_global.h" +#include +#include +#include + //============================================================================================================= // QT INCLUDES //============================================================================================================= @@ -83,11 +88,6 @@ typedef struct { class UTILSSHARED_EXPORT LayoutMaker { public: - //========================================================================================================= - /** - * Constructs a LayoutMaker object. - */ - LayoutMaker(); //========================================================================================================= /** @@ -118,6 +118,36 @@ class UTILSSHARED_EXPORT LayoutMaker bool mirrorXAxis = false, bool mirrorYAxis = false); + //========================================================================================================= + /** + * Reads the specified ANT elc-layout file. + * @param[in] inputPoints The input points in 3D space. + * @param[in, out] outputPoints The output layout points in 2D space. + * @param[in] names The channel names. + * @param[in] outFile The outout file. + * @param[in] do_fit The flag whether to do a sphere fitting. + * @param[in] prad. + * @param[in] w. + * @param[in] h. + * @param[in] writeFile The flag whether to write to file. + * @param[in] mirrorXAxis Mirror points at x axis. + * @param[in] mirrorYAxis Mirror points at y axis. + * + * @return true if making layout was successful, false otherwise. + */ + static bool makeLayout(const std::vector > &inputPoints, + std::vector > &outputPoints, + const std::vector &names, + const std::string& outFilePath, + bool do_fit, + float prad, + float w, + float h, + bool writeFile = false, + bool mirrorXAxis = false, + bool mirrorYAxis = false); + + private: static void sphere_coord(float x, float y, diff --git a/libraries/utils/mnemath.cpp b/libraries/utils/mnemath.cpp index d7ec9e8d30d..f0f14c9d4df 100644 --- a/libraries/utils/mnemath.cpp +++ b/libraries/utils/mnemath.cpp @@ -2,13 +2,14 @@ /** * @file mnemath.cpp * @author Lorenz Esch ; - * Christoph Dinh + * Christoph Dinh ; + * Gabriel Motta * @since 0.1.0 * @date July, 2012 * * @section LICENSE * - * Copyright (C) 2012, Lorenz Esch, Christoph Dinh. All rights reserved. + * Copyright (C) 2012, Lorenz Esch, Christoph Dinh, Gabriel Motta. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that * the following conditions are met: @@ -39,7 +40,7 @@ #define _USE_MATH_DEFINES #include - +#include #include "mnemath.h" //============================================================================================================= @@ -158,6 +159,38 @@ void MNEMath::get_whitener(MatrixXd &A, //============================================================================================================= +void MNEMath::get_whitener(MatrixXd &A, + bool pca, + const std::string& ch_type, + VectorXd &eig, + MatrixXd &eigvec) +{ + // whitening operator + SelfAdjointEigenSolver t_eigenSolver(A);//Can be used because, covariance matrices are self-adjoint matrices. + + eig = t_eigenSolver.eigenvalues(); + eigvec = t_eigenSolver.eigenvectors().transpose(); + + MNEMath::sort(eig, eigvec, false); + qint32 rnk = MNEMath::rank(A); + + for(qint32 i = 0; i < eig.size()-rnk; ++i) + eig(i) = 0; + + printf("Setting small %s eigenvalues to zero.\n", ch_type.c_str()); + if (!pca) // No PCA case. + printf("Not doing PCA for %s\n", ch_type.c_str()); + else + { + printf("Doing PCA for %s.",ch_type.c_str()); + // This line will reduce the actual number of variables in data + // and leadfield to the true rank. + eigvec = eigvec.block(eigvec.rows()-rnk, 0, rnk, eigvec.cols()); + } +} + +//============================================================================================================= + VectorXi MNEMath::intersect(const VectorXi &v1, const VectorXi &v2, VectorXi &idx_sel) @@ -290,6 +323,26 @@ MatrixXd MNEMath::legendre(qint32 n, return y; } + +//============================================================================================================= + +MatrixXd MNEMath::legendre(qint32 n, + const VectorXd &X, + std::string normalize) +{ + MatrixXd y; + + Q_UNUSED(y); + + Q_UNUSED(n); + Q_UNUSED(X); + Q_UNUSED(normalize); + + //ToDo + + return y; +} + //============================================================================================================= SparseMatrix* MNEMath::make_block_diag(const MatrixXd &A, @@ -436,6 +489,86 @@ MatrixXd MNEMath::rescale(const MatrixXd &data, //============================================================================================================= +MatrixXd MNEMath::rescale(const MatrixXd& data, + const RowVectorXf& times, + const std::pair& baseline, + const std::string& mode) +{ + MatrixXd data_out = data; + std::vector valid_modes{"logratio", "ratio", "zscore", "mean", "percent"}; + if(std::find(valid_modes.begin(),valid_modes.end(), mode) == valid_modes.end()) + { + qWarning().noquote() << "[MNEMath::rescale] Mode" << mode.c_str() << "is not supported. Supported modes are:"; + for(auto& mode : valid_modes){ + std::cout << mode << " "; + } + std::cout << "\n" << "Returning input data.\n"; + return data_out; + } + + qInfo().noquote() << QString("[MNEMath::rescale] Applying baseline correction ... (mode: %1)").arg(mode.c_str()); + + qint32 imin = 0; + qint32 imax = times.size(); + + if (baseline.second == baseline.first) { + imin = 0; + } else { + float bmin = baseline.first; + for(qint32 i = 0; i < times.size(); ++i) { + if(times[i] >= bmin) { + imin = i; + break; + } + } + } + + float bmax = baseline.second; + + if (baseline.second == baseline.first) { + bmax = 0; + } + + for(qint32 i = times.size()-1; i >= 0; --i) { + if(times[i] <= bmax) { + imax = i+1; + break; + } + } + + if(imax < imin) { + qWarning() << "[MNEMath::rescale] imax < imin. Returning input data."; + return data_out; + } + + VectorXd mean = data_out.block(0, imin,data_out.rows(),imax-imin).rowwise().mean(); + if(mode.compare("mean") == 0) { + data_out -= mean.rowwise().replicate(data.cols()); + } else if(mode.compare("logratio") == 0) { + for(qint32 i = 0; i < data_out.rows(); ++i) + for(qint32 j = 0; j < data_out.cols(); ++j) + data_out(i,j) = log10(data_out(i,j)/mean[i]); // a value of 1 means 10 times bigger + } else if(mode.compare("ratio") == 0) { + data_out = data_out.cwiseQuotient(mean.rowwise().replicate(data_out.cols())); + } else if(mode.compare("zscore") == 0) { + MatrixXd std_mat = data.block(0, imin, data.rows(), imax-imin) - mean.rowwise().replicate(imax-imin); + std_mat = std_mat.cwiseProduct(std_mat); + VectorXd std_v = std_mat.rowwise().mean(); + for(qint32 i = 0; i < std_v.size(); ++i) + std_v[i] = sqrt(std_v[i] / (float)(imax-imin)); + + data_out -= mean.rowwise().replicate(data_out.cols()); + data_out = data_out.cwiseQuotient(std_v.rowwise().replicate(data_out.cols())); + } else if(mode.compare("percent") == 0) { + data_out -= mean.rowwise().replicate(data_out.cols()); + data_out = data_out.cwiseQuotient(mean.rowwise().replicate(data_out.cols())); + } + + return data_out; +} + +//============================================================================================================= + bool MNEMath::compareTransformation(const MatrixX4f& mDevHeadT, const MatrixX4f& mDevHeadDest, const float& fThreshRot, diff --git a/libraries/utils/mnemath.h b/libraries/utils/mnemath.h index 82c0d3e8279..0cc0b25b122 100644 --- a/libraries/utils/mnemath.h +++ b/libraries/utils/mnemath.h @@ -44,6 +44,10 @@ #include "utils_global.h" +#include +#include +#include + //============================================================================================================= // EIGEN INCLUDES //============================================================================================================= @@ -162,6 +166,21 @@ class UTILSSHARED_EXPORT MNEMath Eigen::VectorXd& eig, Eigen::MatrixXd& eigvec); + //========================================================================================================= + /** + * Returns the whitener of a given matrix. + * + * @param[in] A Matrix to compute the whitener from. + * @param[in] pca perform a pca. + * + * @return rank of matrix A. + */ + static void get_whitener(Eigen::MatrixXd& A, + bool pca, + const std::string& ch_type, + Eigen::VectorXd& eig, + Eigen::MatrixXd& eigvec); + //========================================================================================================= /** * Find the intersection of two vectors @@ -202,6 +221,21 @@ class UTILSSHARED_EXPORT MNEMath const Eigen::VectorXd &X, QString normalize = QString("unnorm")); + //========================================================================================================= + /** + * LEGENDRE Associated Legendre function. + * + * P = LEGENDRE(N,X) computes the associated Legendre functions + * of degree N and order M = 0, 1, ..., N, evaluated for each element + * of X. N must be a scalar integer and X must contain real values + * between -1 <= X <= 1. + * + * @return associated Legendre functions. + */ + static Eigen::MatrixXd legendre(qint32 n, + const Eigen::VectorXd &X, + std::string normalize = "unnorm"); + //========================================================================================================= /** * ToDo make this a template function @@ -266,6 +300,27 @@ class UTILSSHARED_EXPORT MNEMath const QPair &baseline, QString mode); + //========================================================================================================= + /** + * ToDo: Maybe new processing class + * + * Rescale aka baseline correct data + * + * @param[in] data Data Matrix (m x n_time). + * @param[in] times Time instants is seconds. + * @param[in] baseline If baseline is (a, b) the interval is between "a (s)" and "b (s)". + * If a and b are equal use interval between the beginning of the data and the time point 0 (stimulus onset). + * @param[in] mode Do baseline correction with ratio (power is divided by mean power during baseline) or zscore (power is divided by standard. + * deviatio of power during baseline after substracting the mean, power = [power - mean(power_baseline)] / std(power_baseline)). + * ("logratio" | "ratio" | "zscore" | "mean" | "percent") + * + * @return rescaled data matrix rescaling. + */ + static Eigen::MatrixXd rescale(const Eigen::MatrixXd& data, + const Eigen::RowVectorXf& times, + const std::pair& baseline, + const std::string& mode); + //========================================================================================================= /** * Sorts a vector (ascending order) in place and returns the track of the original indeces diff --git a/libraries/utils/selectionio.cpp b/libraries/utils/selectionio.cpp index 277c479b882..bf7594803d8 100644 --- a/libraries/utils/selectionio.cpp +++ b/libraries/utils/selectionio.cpp @@ -2,13 +2,14 @@ /** * @file selectionio.cpp * @author Lorenz Esch ; - * Christoph Dinh + * Christoph Dinh ; + * Gabriel Motta * @since 0.1.0 * @date October, 2014 * * @section LICENSE * - * Copyright (C) 2014, Lorenz Esch, Christoph Dinh. All rights reserved. + * Copyright (C) 2014, Lorenz Esch, Christoph Dinh, Gabriel Motta. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that * the following conditions are met: @@ -40,6 +41,8 @@ #include "selectionio.h" #include +#include +#include //============================================================================================================= // QT INCLUDES @@ -112,6 +115,48 @@ bool SelectionIO::readMNESelFile(QString path, QMap &select //============================================================================================================= +bool SelectionIO::readMNESelFile(const std::string& path, std::multimap> &selectionMap) +{ + //Open .sel file + if(path.find(".sel") != std::string::npos) + return false; + + //clear the map first + selectionMap.clear(); + + std::ifstream inFile(path); + if(!inFile.is_open()){ + qDebug()<<"Error opening selection file"; + return false; + } + + std::string line; + + while(std::getline(inFile, line)){ + if(line.find('%') == std::string::npos && line.find(':') != std::string::npos){ + std::stringstream stream{line}; + std::vector firstSplit; + for (std::string element; std::getline(stream, line, ':');){ + firstSplit.push_back(std::move(element)); + } + std::string key = firstSplit.at(0); + + std::vector secondSplit; + for (std::string element; std::getline(stream, line, '|');){ + secondSplit.push_back(std::move(element)); + } + if(secondSplit.back() == ""){ + secondSplit.pop_back(); + } + selectionMap.insert({key, secondSplit}); + } + } + + return true; +} + +//============================================================================================================= + bool SelectionIO::readBrainstormMonFile(QString path, QMap &selectionMap) { //Open .sel file @@ -152,6 +197,44 @@ bool SelectionIO::readBrainstormMonFile(QString path, QMap //============================================================================================================= +bool SelectionIO::readBrainstormMonFile(const std::string& path, std::multimap>& selectionMap) +{ + //Open file + if(path.find(".mon") != std::string::npos) + return false; + + //clear the map first + selectionMap.clear(); + + std::ifstream inFile(path); + if(!inFile.is_open()){ + qDebug()<<"Error opening montage file"; + return false; + } + std::vector channels; + + std::string groupName; + std::getline(inFile, groupName); + + std::string line; + while(std::getline(inFile, line)){ + if(line.find(':') != std::string::npos){ + std::stringstream stream{line}; + std::vector split; + for (std::string element; std::getline(stream, line, ':');){ + split.push_back(std::move(element)); + } + channels.push_back(split.at(0)); + } + } + + selectionMap.insert({groupName, channels}); + + return true; +} + +//============================================================================================================= + bool SelectionIO::writeMNESelFile(QString path, const QMap &selectionMap) { //Open .sel file @@ -186,6 +269,31 @@ bool SelectionIO::writeMNESelFile(QString path, const QMap //============================================================================================================= +bool SelectionIO::writeMNESelFile(const std::string& path, const std::map>& selectionMap) +{ + //Open .sel file + if(path.find(".sel") == std::string::npos) + return false; + + std::ofstream outFile(path); + if (outFile.is_open()){ + qDebug()<<"Error opening sel file for writing"; + return false; + } + + for(auto& mapElement : selectionMap){ + outFile << mapElement.first << ":"; + for(auto& vectorElement : mapElement.second){ + outFile << vectorElement << "|"; + } + outFile << "\n" << "\n"; + } + + return true; +} + +//============================================================================================================= + bool SelectionIO::writeBrainstormMonFiles(QString path, const QMap &selectionMap) { //Open .sel file @@ -221,3 +329,29 @@ bool SelectionIO::writeBrainstormMonFiles(QString path, const QMap>& selectionMap) +{ + //Open file + if(path.find(".mon") == std::string::npos) + return false; + + for(auto& mapElement : selectionMap){ + //logic of using parent_path here might not be "correct" + std::string newPath{path.substr(0, path.find_last_of("/") + 1) + mapElement.first + ".mon"}; + std::ofstream outFile(newPath); + if(!outFile.is_open()){ + qDebug()<<"Error opening mon file for writing"; + return false; + } + + outFile << mapElement.first << "\n"; + for(auto& vectorElement : mapElement.second){ + outFile << vectorElement << " : " << vectorElement << "\n"; + } + } + + return true; +} diff --git a/libraries/utils/selectionio.h b/libraries/utils/selectionio.h index 458a98798dd..2f6fbe329d6 100644 --- a/libraries/utils/selectionio.h +++ b/libraries/utils/selectionio.h @@ -2,13 +2,14 @@ /** * @file selectionio.h * @author Lorenz Esch ; - * Christoph Dinh + * Christoph Dinh ; + * Gabriel Motta * @since 0.1.0 * @date October, 2014 * * @section LICENSE * - * Copyright (C) 2014, Lorenz Esch, Christoph Dinh. All rights reserved. + * Copyright (C) 2014, Lorenz Esch, Christoph Dinh, Gabriel Motta. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that * the following conditions are met: @@ -42,6 +43,10 @@ #include "utils_global.h" +#include +#include +#include + //============================================================================================================= // QT INCLUDES //============================================================================================================= @@ -83,6 +88,14 @@ class UTILSSHARED_EXPORT SelectionIO */ static bool readMNESelFile(QString path, QMap &selectionMap); + //========================================================================================================= + /** + * Reads the specified MNE sel file. + * @param[in] path holds the file path of the .sel file which is to be read. + * @param[in] selectionMap holds the map to which the read selection groups are stored. + */ + static bool readMNESelFile(const std::string& path, std::multimap>& selectionMap); + //========================================================================================================= /** * Reads the specified Brainstorm montage file. @@ -91,6 +104,14 @@ class UTILSSHARED_EXPORT SelectionIO */ static bool readBrainstormMonFile(QString path, QMap &selectionMap); + //========================================================================================================= + /** + * Reads the specified Brainstorm montage file. + * @param[in] path holds the file path of the .mon file which is to be read. + * @param[in] selectionMap holds the map to which the read selection groups are stored. + */ + static bool readBrainstormMonFile(const std::string& path, std::multimap>& selectionMap); + //========================================================================================================= /** * Writes the specified selection groups to a single MNE .sel file. @@ -99,6 +120,14 @@ class UTILSSHARED_EXPORT SelectionIO */ static bool writeMNESelFile(QString path, const QMap &selectionMap); + //========================================================================================================= + /** + * Writes the specified selection groups to a single MNE .sel file. + * @param[in] path holds the file path of the .sel file which is to be read. + * @param[in] selectionMap holds the map to which the read selection groups are stored. + */ + static bool writeMNESelFile(const std::string& path, const std::map>& selectionMap); + //========================================================================================================= /** * Writes the specified selection groups to different Brainstorm .mon files. The amount of written files depend on the number of selection groups in selectionMap @@ -106,6 +135,14 @@ class UTILSSHARED_EXPORT SelectionIO * @param[in] selectionMap holds the map to which the read selection groups are stored. */ static bool writeBrainstormMonFiles(QString path, const QMap &selectionMap); + + //========================================================================================================= + /** + * Writes the specified selection groups to different Brainstorm .mon files. The amount of written files depend on the number of selection groups in selectionMap + * @param[in] path holds the file path of the .mon file which is to be read. + * @param[in] selectionMap holds the map to which the read selection groups are stored. + */ + static bool writeBrainstormMonFiles(const std::string& path, const std::map>& selectionMap); }; } // NAMESPACE diff --git a/libraries/utils/spectral.cpp b/libraries/utils/spectral.cpp index cb54066a193..f7729b90c98 100644 --- a/libraries/utils/spectral.cpp +++ b/libraries/utils/spectral.cpp @@ -2,13 +2,14 @@ /** * @file spectral.cpp * @author Daniel Strohmeier ; - * Lorenz Esch + * Lorenz Esch ; + * Gabriel Motta * @since 0.1.0 * @date March, 2018 * * @section LICENSE * - * Copyright (C) 2018, Daniel Strohmeier, Lorenz Esch. All rights reserved. + * Copyright (C) 2018, Daniel Strohmeier, Lorenz Esch, Gabriel Motta. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that * the following conditions are met: @@ -186,6 +187,14 @@ void Spectral::reduce(QVector& finalData, //============================================================================================================= +//void Spectral::reduce(std::vector& finalData, +// const MatrixXcd& resultData) +//{ +// finalData.push_back(resultData); +//} + +//============================================================================================================= + Eigen::RowVectorXd Spectral::psdFromTaperedSpectra(const Eigen::MatrixXcd &matTapSpectrum, const Eigen::VectorXd &vecTapWeights, int iNfft, @@ -298,6 +307,24 @@ QPair Spectral::generateTapers(int iSignalLength, const QStr //============================================================================================================= +std::pair Spectral::generateTapers(int iSignalLength, const std::string &sWindowType) +{ + std::pair pairOut; + if (sWindowType == "hanning") { + pairOut.first = hanningWindow(iSignalLength); + pairOut.second = VectorXd::Ones(1); + } else if (sWindowType == "ones") { + pairOut.first = MatrixXd::Ones(1, iSignalLength) / double(iSignalLength); + pairOut.second = VectorXd::Ones(1); + } else { + pairOut.first = hanningWindow(iSignalLength); + pairOut.second = VectorXd::Ones(1); + } + return pairOut; +} + +//============================================================================================================= + MatrixXd Spectral::hanningWindow(int iSignalLength) { MatrixXd matHann = MatrixXd::Zero(1, iSignalLength); diff --git a/libraries/utils/spectral.h b/libraries/utils/spectral.h index 5b67a637ab8..e8e1eb45cd3 100644 --- a/libraries/utils/spectral.h +++ b/libraries/utils/spectral.h @@ -2,13 +2,14 @@ /** * @file spectral.h * @author Daniel Strohmeier ; - * Lorenz Esch + * Lorenz Esch ; + * Gabriel Motta * @since 0.1.0 * @date March, 2018 * * @section LICENSE * - * Copyright (C) 2018, Daniel Strohmeier, Lorenz Esch. All rights reserved. + * Copyright (C) 2018, Daniel Strohmeier, Lorenz Esch, Gabriel Motta. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that * the following conditions are met: @@ -45,6 +46,9 @@ #include "utils_global.h" +#include +#include + //============================================================================================================= // EIGEN INCLUDES //============================================================================================================= @@ -118,6 +122,22 @@ class UTILSSHARED_EXPORT Spectral int iNfft, bool bUseThreads = true); +// //========================================================================================================= +// /** +// * Calculates the full tapered spectra of a given input matrix data. This function calculates each row in parallel. +// * +// * @param[in] matData input matrix data (time domain), for which the spectrum is computed. +// * @param[in] matTaper tapers used to compute the spectra. +// * @param[in] iNfft FFT length. +// * @param[in] bUseThreads Whether to use multiple threads. +// * +// * @return tapered spectra of the input data. +// */ +// static std::vector computeTaperedSpectraMatrix(const Eigen::MatrixXd &matData, +// const Eigen::MatrixXd &matTaper, +// int iNfft, +// bool bUseThreads = true); + //========================================================================================================= /** * Computes the tapered spectra for a row vector. This function gets called in parallel. @@ -138,6 +158,16 @@ class UTILSSHARED_EXPORT Spectral static void reduce(QVector& finalData, const Eigen::MatrixXcd& resultData); +// //========================================================================================================= +// /** +// * Reduces the taperedSpectra results to a final result. This function gets called in parallel. +// * +// * @param[out] finalData The final data data. +// * @param[in] resultData The resulting data from the computation step. +// */ +// static void reduce(std::vector& finalData, +// const Eigen::MatrixXcd& resultData); + //========================================================================================================= /** * Calculates the power spectral density of given tapered spectrum @@ -197,6 +227,18 @@ class UTILSSHARED_EXPORT Spectral static QPair generateTapers(int iSignalLength, const QString &sWindowType = "hanning"); + //========================================================================================================= + /** + * Calculates a hanning window of given length + * + * @param[in] iSignalLength length of the hanning window. + * @param[in] sWindowType type of the window function used to compute tapered spectra. + * + * @return Qpair of tapers and taper weights. + */ + static std::pair generateTapers(int iSignalLength, + const std::string &sWindowType = "hanning"); + private: //========================================================================================================= /** diff --git a/libraries/utils/warp.cpp b/libraries/utils/warp.cpp index 87075c07ade..13d9ad2444a 100644 --- a/libraries/utils/warp.cpp +++ b/libraries/utils/warp.cpp @@ -1,13 +1,14 @@ //============================================================================================================= /** * @file warp.cpp - * @author Lorenz Esch + * @author Lorenz Esch ; + * Gabriel Motta * @since 0.1.0 * @date November, 2015 * * @section LICENSE * - * Copyright (C) 2015, Lorenz Esch. All rights reserved. + * Copyright (C) 2015, Lorenz Esch, Gabriel Motta. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that * the following conditions are met: @@ -38,6 +39,7 @@ #include "warp.h" #include +#include //============================================================================================================= // EIGEN INCLUDES @@ -185,3 +187,50 @@ MatrixXf Warp::readsLm(const QString &electrodeFileName) } return electrodes; } + +//============================================================================================================= + +MatrixXf Warp::readsLm(const std::string &electrodeFileName) +{ + MatrixXf electrodes; + std::ifstream inFile(electrodeFileName); + + if(!inFile.is_open()) { + qDebug()<<"Error opening file"; + //Why are we not returning? +// return false; + } + + //Start reading from file + double numberElectrodes; + int i = 0; + + std::string line; + while(std::getline(inFile, line)){ + std::vector fields; + std::stringstream stream{line}; + std::string element; + + stream >> std::ws; + while(stream >> element){ + fields.push_back(std::move(element)); + stream >> std::ws; + } + + //Read number of electrodes + if(i == 0){ + numberElectrodes = std::stod(fields.at(fields.size()-1)); + electrodes = MatrixXf::Zero(numberElectrodes, 3); + } + + //Read actual electrode positions + else{ + Vector3f x; + x << std::stof(fields.at(fields.size()-3)), std::stof(fields.at(fields.size()-2)), std::stof(fields.at(fields.size()-1)); + electrodes.row(i-1)=x.transpose(); + } + i++; + } + + return electrodes; +} diff --git a/libraries/utils/warp.h b/libraries/utils/warp.h index a3f251767a0..9bf40c5947f 100644 --- a/libraries/utils/warp.h +++ b/libraries/utils/warp.h @@ -112,6 +112,16 @@ class UTILSSHARED_EXPORT Warp */ Eigen::MatrixXf readsLm(const QString &electrodeFileName); + //========================================================================================================= + /** + * Read electrode positions from MRI Database + * + * @param[in] electrodeFileName .txt file of electrodes. + * + * @return electrodes Matrix with electrode positions. + */ + Eigen::MatrixXf readsLm(const std::string &electrodeFileName); + private: //========================================================================================================= diff --git a/testframes/test_utils_circularbuffer/test_utils_circularbuffer.cpp b/testframes/test_utils_circularbuffer/test_utils_circularbuffer.cpp new file mode 100644 index 00000000000..0ca338ff66a --- /dev/null +++ b/testframes/test_utils_circularbuffer/test_utils_circularbuffer.cpp @@ -0,0 +1,147 @@ +//============================================================================================================= +/** + * @file test_utils_circularbuffer.cpp + * @author Gabriel Motta + * @since 0.1.9 + * @date August, 2022 + * + * @section LICENSE + * + * Copyright (C) 2022, Gabriel Motta. 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 MNE-CPP authors 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. + * + * + * @brief Test for the circular buffer. + * + */ + +//============================================================================================================= +// INCLUDES +//============================================================================================================= + +#include + +//============================================================================================================= +// QT INCLUDES +//============================================================================================================= + +#include +#include +#include +#include + +//============================================================================================================= +// USED NAMESPACES +//============================================================================================================= + +using namespace UTILSLIB; + +//============================================================================================================= +/** + * DECLARE CLASS TestCircularBuffer + * + * @brief The TestCircularBuffer class provides tests for verifying circular buffer functionality + * + */ + +class TestCircularBuffer : public QObject +{ + Q_OBJECT + +private slots: + void testBufferCreationDestruction(); + void testBufferPushingPopping(); + void testBufferCapacity(); +}; + +//============================================================================================================= + +void TestCircularBuffer::testBufferCreationDestruction() +{ + //Test Constructors/Destructors + { + CircularBuffer testBuffer(10); + } + { + CircularBuffer *testBuffer = new CircularBuffer(10); + delete testBuffer; + } +} + +//============================================================================================================= + +void TestCircularBuffer::testBufferPushingPopping() +{ + CircularBuffer testBuffer(10); + + int testVal = 5; + int testArray[3] = {1, 2, 3}; + + //Verify Push + QVERIFY(testBuffer.push(testVal)); + QVERIFY(testBuffer.push(testArray, 3)); + + int resultVal = 0; + int resultArray[3] = {0, 0, 0}; + + QVERIFY(testBuffer.pop(resultVal)); + for (int i = 0; i < 3; ++i){ + QVERIFY(testBuffer.pop(resultArray[i])); + } + + QVERIFY(resultVal == testVal); + for (int i = 0; i < 3; ++i){ + QVERIFY(resultArray[i] == resultArray[i]); + } +} + +//============================================================================================================= + +void TestCircularBuffer::testBufferCapacity() +{ + CircularBuffer testBuffer(2); + int testSink = 0; + + QVERIFY(!testBuffer.pop(testSink)); + + testBuffer.push(5); + testBuffer.push(10); + + QVERIFY(!testBuffer.push(15)); + + testBuffer.clear(); + + QVERIFY(!testBuffer.pop(testSink)); + + QVERIFY(testBuffer.push(20)); + + QVERIFY(testBuffer.pop(testSink)); + + QVERIFY(testSink == 20); + + QVERIFY(!testBuffer.pop(testSink)); +} + +//============================================================================================================= +// MAIN +//============================================================================================================= + +QTEST_GUILESS_MAIN(TestCircularBuffer); +#include "test_utils_circularbuffer.moc" diff --git a/testframes/test_utils_circularbuffer/test_utils_circularbuffer.pro b/testframes/test_utils_circularbuffer/test_utils_circularbuffer.pro new file mode 100644 index 00000000000..4e2b8f3cbc6 --- /dev/null +++ b/testframes/test_utils_circularbuffer/test_utils_circularbuffer.pro @@ -0,0 +1,109 @@ +#============================================================================================================== +# +# @file test_coregistration.pro +# @author Gabriel Motta +# @since 0.1.9 +# @date January, 2022 +# +# @section LICENSE +# +# Copyright (C) 2022, Gabriel Motta. 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 MNE-CPP authors 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. +# +# +# @brief This project file generates the makefile to build the test_utils_circularbuffer test. +# +#============================================================================================================== + +include(../../mne-cpp.pri) + +TEMPLATE = app + +VERSION = $${MNE_CPP_VERSION} + +QT += testlib network +QT -= gui + +CONFIG += console +!contains(MNECPP_CONFIG, withAppBundles) { + CONFIG -= app_bundle +} + +DESTDIR = $${MNE_BINARY_DIR} + +TARGET = test_utils_circularbuffer +CONFIG(debug, debug|release) { + TARGET = $$join(TARGET,,,d) +} + +contains(MNECPP_CONFIG, static) { + CONFIG += static + DEFINES += STATICBUILD +} + +LIBS += -L$${MNE_LIBRARY_DIR} +CONFIG(debug, debug|release) { + LIBS += -lmnecppUtilsd \ +} else { + LIBS += -lmnecppUtils \ +} + +SOURCES += \ + test_utils_circularbuffer.cpp + +clang { + QMAKE_CXXFLAGS += -isystem $${EIGEN_INCLUDE_DIR} +} else { + INCLUDEPATH += $${EIGEN_INCLUDE_DIR} +} +INCLUDEPATH += $${MNE_INCLUDE_DIR} + +contains(MNECPP_CONFIG, withCodeCov) { + QMAKE_CXXFLAGS += --coverage + QMAKE_LFLAGS += --coverage +} + +unix:!macx { + QMAKE_RPATHDIR += $ORIGIN/../lib +} + +macx { + QMAKE_LFLAGS += -Wl,-rpath,@executable_path/../lib +} + +# Activate FFTW backend in Eigen for non-static builds only +contains(MNECPP_CONFIG, useFFTW):!contains(MNECPP_CONFIG, static) { + DEFINES += EIGEN_FFTW_DEFAULT + INCLUDEPATH += $$shell_path($${FFTW_DIR_INCLUDE}) + LIBS += -L$$shell_path($${FFTW_DIR_LIBS}) + + win32 { + # On Windows + LIBS += -llibfftw3-3 \ + -llibfftw3f-3 \ + -llibfftw3l-3 \ + } + + unix:!macx { + # On Linux + LIBS += -lfftw3 \ + -lfftw3_threads \ + } +} diff --git a/testframes/testframes.pro b/testframes/testframes.pro index edb1e28e7ba..5cb82f9c5fe 100644 --- a/testframes/testframes.pro +++ b/testframes/testframes.pro @@ -58,6 +58,7 @@ SUBDIRS += \ test_fiff_digitizer \ test_mne_msh_display_surface_set \ test_mne_project_to_surface \ + test_utils_circularbuffer qtHaveModule(charts) { SUBDIRS += \