diff --git a/.cproject b/.cproject new file mode 100644 index 0000000000..366fbdc2b3 --- /dev/null +++ b/.cproject @@ -0,0 +1,663 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cmake + -E chdir build/ cmake -G "MinGW Makefiles" -D CMAKE_BUILD_TYPE=Debug ../ + true + false + true + + + cmake + -E chdir build/ cmake -G "MinGW Makefiles" -D CMAKE_BUILD_TYPE=Release ../ + true + false + true + + + cmake + -E chdir build/ cmake -G "Unix Makefiles" -D CMAKE_BUILD_TYPE=Debug ../ + true + false + true + + + cmake + -E chdir build/ cmake -G "Unix Makefiles" -D CMAKE_BUILD_TYPE=Release ../ + + true + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cmake + -E chdir build/ cmake -G "MinGW Makefiles" -D CMAKE_BUILD_TYPE=Debug ../ + true + false + true + + + cmake + -E chdir build/ cmake -G "MinGW Makefiles" -D CMAKE_BUILD_TYPE=Release ../ + true + false + true + + + cmake + -E chdir build/ cmake -G "Unix Makefiles" -D CMAKE_BUILD_TYPE=Debug ../ + true + false + true + + + cmake + -E chdir build/ cmake -G "Unix Makefiles" -D CMAKE_BUILD_TYPE=Release ../ + + true + false + true + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000000..4deb1e63f6 --- /dev/null +++ b/.project @@ -0,0 +1,82 @@ + + + rtabmap + + + + + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + clean,full,incremental, + + + ?name? + + + + org.eclipse.cdt.make.core.append_environment + true + + + org.eclipse.cdt.make.core.autoBuildTarget + all + + + org.eclipse.cdt.make.core.buildArguments + -C ${ProjDirPath}/build VERBOSE=true + + + org.eclipse.cdt.make.core.buildCommand + make + + + org.eclipse.cdt.make.core.buildLocation + ${workspace_loc:/RTAB-Map} + + + org.eclipse.cdt.make.core.cleanBuildTarget + clean + + + org.eclipse.cdt.make.core.contents + org.eclipse.cdt.make.core.activeConfigSettings + + + org.eclipse.cdt.make.core.enableAutoBuild + false + + + org.eclipse.cdt.make.core.enableCleanBuild + true + + + org.eclipse.cdt.make.core.enableFullBuild + true + + + org.eclipse.cdt.make.core.fullBuildTarget + all + + + org.eclipse.cdt.make.core.stopOnError + true + + + org.eclipse.cdt.make.core.useDefaultBuildCmd + false + + + + + org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder + + + + + + org.eclipse.cdt.core.cnature + org.eclipse.cdt.core.ccnature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + org.eclipse.cdt.managedbuilder.core.ScannerConfigNature + + diff --git a/3rdParty/include/cppunit/AdditionalMessage.h b/3rdParty/include/cppunit/AdditionalMessage.h new file mode 100644 index 0000000000..917d75414e --- /dev/null +++ b/3rdParty/include/cppunit/AdditionalMessage.h @@ -0,0 +1,76 @@ +#ifndef CPPUNIT_ADDITIONALMESSAGE_H +#define CPPUNIT_ADDITIONALMESSAGE_H + +#include + + +CPPUNIT_NS_BEGIN + + +/*! \brief An additional Message for assertions. + * \ingroup CreatingNewAssertions + * + * Provides a implicit constructor that takes a single string. This allow this + * class to be used as the message arguments in macros. + * + * The constructed object is either a Message with a single detail string if + * a string was passed to the macro, or a copy of the Message passed to the macro. + * + * Here is an example of usage: + * \code + * + * void checkStringEquals( const std::string &expected, + * const std::string &actual, + * const CppUnit::SourceLine &sourceLine, + * const CppUnit::AdditionalMessage &message ); + * + * #define XTLUT_ASSERT_STRING_EQUAL_MESSAGE( expected, actual, message ) \ + * ::XtlUt::Impl::checkStringEquals( ::Xtl::toString(expected), \ + * ::Xtl::toString(actual), \ + * CPPUNIT_SOURCELINE(), \ + * message ) + * \endcode + * + * In the previous example, the user can specify a simple string for \a message, + * or a complex Message object. + * + * \see Message + */ +class CPPUNIT_API AdditionalMessage : public Message +{ +public: + typedef Message SuperClass; + + /// Constructs an empty Message. + AdditionalMessage(); + + /*! \brief Constructs a Message with the specified detail string. + * \param detail1 Detail string of the message. If empty, then it is not added. + */ + AdditionalMessage( const std::string &detail1 ); + + /*! \brief Constructs a Message with the specified detail string. + * \param detail1 Detail string of the message. If empty, then it is not added. + */ + AdditionalMessage( const char *detail1 ); + + /*! \brief Constructs a copy of the specified message. + * \param other Message to copy. + */ + AdditionalMessage( const Message &other ); + + /*! \brief Assignment operator. + * \param other Message to copy. + * \return Reference on this object. + */ + AdditionalMessage &operator =( const Message &other ); + +private: +}; + + +CPPUNIT_NS_END + + + +#endif // CPPUNIT_ADDITIONALMESSAGE_H diff --git a/3rdParty/include/cppunit/Asserter.h b/3rdParty/include/cppunit/Asserter.h new file mode 100644 index 0000000000..94dadaad71 --- /dev/null +++ b/3rdParty/include/cppunit/Asserter.h @@ -0,0 +1,143 @@ +#ifndef CPPUNIT_ASSERTER_H +#define CPPUNIT_ASSERTER_H + +#include +#include +#include + +CPPUNIT_NS_BEGIN + + +class Message; + + +/*! \brief A set of functions to help writing assertion macros. + * \ingroup CreatingNewAssertions + * + * Here is an example of assertion, a simplified version of the + * actual assertion implemented in examples/cppunittest/XmlUniformiser.h: + * \code + * #include + * #include + * + * void + * checkXmlEqual( std::string expectedXml, + * std::string actualXml, + * CppUnit::SourceLine sourceLine ) + * { + * std::string expected = XmlUniformiser( expectedXml ).stripped(); + * std::string actual = XmlUniformiser( actualXml ).stripped(); + * + * if ( expected == actual ) + * return; + * + * ::CppUnit::Asserter::failNotEqual( expected, + * actual, + * sourceLine ); + * } + * + * /// Asserts that two XML strings are equivalent. + * #define CPPUNITTEST_ASSERT_XML_EQUAL( expected, actual ) \ + * checkXmlEqual( expected, actual, \ + * CPPUNIT_SOURCELINE() ) + * \endcode + */ +struct Asserter +{ + /*! \brief Throws a Exception with the specified message and location. + */ + static void CPPUNIT_API fail( const Message &message, + const SourceLine &sourceLine = SourceLine() ); + + /*! \brief Throws a Exception with the specified message and location. + * \deprecated Use fail( Message, SourceLine ) instead. + */ + static void CPPUNIT_API fail( std::string message, + const SourceLine &sourceLine = SourceLine() ); + + /*! \brief Throws a Exception with the specified message and location. + * \param shouldFail if \c true then the exception is thrown. Otherwise + * nothing happen. + * \param message Message explaining the assertion failiure. + * \param sourceLine Location of the assertion. + */ + static void CPPUNIT_API failIf( bool shouldFail, + const Message &message, + const SourceLine &sourceLine = SourceLine() ); + + /*! \brief Throws a Exception with the specified message and location. + * \deprecated Use failIf( bool, Message, SourceLine ) instead. + * \param shouldFail if \c true then the exception is thrown. Otherwise + * nothing happen. + * \param message Message explaining the assertion failiure. + * \param sourceLine Location of the assertion. + */ + static void CPPUNIT_API failIf( bool shouldFail, + std::string message, + const SourceLine &sourceLine = SourceLine() ); + + /*! \brief Returns a expected value string for a message. + * Typically used to create 'not equal' message, or to check that a message + * contains the expected content when writing unit tests for your custom + * assertions. + * + * \param expectedValue String that represents the expected value. + * \return \a expectedValue prefixed with "Expected: ". + * \see makeActual(). + */ + static std::string CPPUNIT_API makeExpected( const std::string &expectedValue ); + + /*! \brief Returns an actual value string for a message. + * Typically used to create 'not equal' message, or to check that a message + * contains the expected content when writing unit tests for your custom + * assertions. + * + * \param actualValue String that represents the actual value. + * \return \a actualValue prefixed with "Actual : ". + * \see makeExpected(). + */ + static std::string CPPUNIT_API makeActual( const std::string &actualValue ); + + static Message CPPUNIT_API makeNotEqualMessage( const std::string &expectedValue, + const std::string &actualValue, + const AdditionalMessage &additionalMessage = AdditionalMessage(), + const std::string &shortDescription = "equality assertion failed"); + + /*! \brief Throws an Exception with the specified message and location. + * \param expected Text describing the expected value. + * \param actual Text describing the actual value. + * \param sourceLine Location of the assertion. + * \param additionalMessage Additional message. Usually used to report + * what are the differences between the expected and actual value. + * \param shortDescription Short description for the failure message. + */ + static void CPPUNIT_API failNotEqual( std::string expected, + std::string actual, + const SourceLine &sourceLine, + const AdditionalMessage &additionalMessage = AdditionalMessage(), + std::string shortDescription = "equality assertion failed" ); + + /*! \brief Throws an Exception with the specified message and location. + * \param shouldFail if \c true then the exception is thrown. Otherwise + * nothing happen. + * \param expected Text describing the expected value. + * \param actual Text describing the actual value. + * \param sourceLine Location of the assertion. + * \param additionalMessage Additional message. Usually used to report + * where the "difference" is located. + * \param shortDescription Short description for the failure message. + */ + static void CPPUNIT_API failNotEqualIf( bool shouldFail, + std::string expected, + std::string actual, + const SourceLine &sourceLine, + const AdditionalMessage &additionalMessage = AdditionalMessage(), + std::string shortDescription = "equality assertion failed" ); + +}; + + +CPPUNIT_NS_END + + +#endif // CPPUNIT_ASSERTER_H diff --git a/3rdParty/include/cppunit/BriefTestProgressListener.h b/3rdParty/include/cppunit/BriefTestProgressListener.h new file mode 100644 index 0000000000..137ca44b31 --- /dev/null +++ b/3rdParty/include/cppunit/BriefTestProgressListener.h @@ -0,0 +1,43 @@ +#ifndef CPPUNIT_BRIEFTESTPROGRESSLISTENER_H +#define CPPUNIT_BRIEFTESTPROGRESSLISTENER_H + +#include + + +CPPUNIT_NS_BEGIN + + +/*! \brief TestListener that prints the name of each test before running it. + * \ingroup TrackingTestExecution + */ +class CPPUNIT_API BriefTestProgressListener : public TestListener +{ +public: + /*! Constructs a BriefTestProgressListener object. + */ + BriefTestProgressListener(); + + /// Destructor. + virtual ~BriefTestProgressListener(); + + void startTest( Test *test ); + + void addFailure( const TestFailure &failure ); + + void endTest( Test *test ); + +private: + /// Prevents the use of the copy constructor. + BriefTestProgressListener( const BriefTestProgressListener © ); + + /// Prevents the use of the copy operator. + void operator =( const BriefTestProgressListener © ); + +private: + bool m_lastTestFailed; +}; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_BRIEFTESTPROGRESSLISTENER_H diff --git a/3rdParty/include/cppunit/CompilerOutputter.h b/3rdParty/include/cppunit/CompilerOutputter.h new file mode 100644 index 0000000000..885fe65265 --- /dev/null +++ b/3rdParty/include/cppunit/CompilerOutputter.h @@ -0,0 +1,146 @@ +#ifndef CPPUNIT_COMPILERTESTRESULTOUTPUTTER_H +#define CPPUNIT_COMPILERTESTRESULTOUTPUTTER_H + +#include +#include +#include + +CPPUNIT_NS_BEGIN + + +class Exception; +class SourceLine; +class Test; +class TestFailure; +class TestResultCollector; + +/*! + * \brief Outputs a TestResultCollector in a compiler compatible format. + * \ingroup WritingTestResult + * + * Printing the test results in a compiler compatible format (assertion + * location has the same format as compiler error), allow you to use your + * IDE to jump to the assertion failure. Location format can be customized (see + * setLocationFormat() ). + * + * For example, when running the test in a post-build with VC++, if an assertion + * fails, you can jump to the assertion by pressing F4 (jump to next error). + * + * Heres is an example of usage (from examples/cppunittest/CppUnitTestMain.cpp): + * \code + * int main( int argc, char* argv[] ) { + * // if command line contains "-selftest" then this is the post build check + * // => the output must be in the compiler error format. + * bool selfTest = (argc > 1) && + * (std::string("-selftest") == argv[1]); + * + * CppUnit::TextUi::TestRunner runner; + * runner.addTest( CppUnitTest::suite() ); // Add the top suite to the test runner + * + * if ( selfTest ) + * { // Change the default outputter to a compiler error format outputter + * // The test runner owns the new outputter. + * runner.setOutputter( new CppUnit::CompilerOutputter( &runner.result(), + * std::cerr ) ); + * } + * + * // Run the test and don't wait a key if post build check. + * bool wasSuccessful = runner.run( "", !selfTest ); + * + * // Return error code 1 if the one of test failed. + * return wasSuccessful ? 0 : 1; + * } + * \endcode + */ +class CPPUNIT_API CompilerOutputter : public Outputter +{ +public: + /*! \brief Constructs a CompilerOutputter object. + * \param result Result of the test run. + * \param stream Stream used to output test result. + * \param locationFormat Error location format used by your compiler. Default + * to \c CPPUNIT_COMPILER_LOCATION_FORMAT which is defined + * in the configuration file. See setLocationFormat() for detail. + * \see setLocationFormat(). + */ + CompilerOutputter( TestResultCollector *result, + OStream &stream, + const std::string &locationFormat = CPPUNIT_COMPILER_LOCATION_FORMAT ); + + /// Destructor. + virtual ~CompilerOutputter(); + + /*! \brief Sets the error location format. + * + * Indicates the format used to report location of failed assertion. This format should + * match the one used by your compiler. + * + * The location format is a string in which the occurence of the following character + * sequence are replaced: + * + * - "%l" => replaced by the line number + * - "%p" => replaced by the full path name of the file ("G:\prg\vc\cppunit\MyTest.cpp") + * - "%f" => replaced by the base name of the file ("MyTest.cpp") + * + * Some examples: + * + * - VC++ error location format: "%p(%l):" => produce "G:\prg\MyTest.cpp(43):" + * - GCC error location format: "%f:%l:" => produce "MyTest.cpp:43:" + * + * Thoses are the two compilers currently supported (gcc format is used if + * VC++ is not detected). If you want your compiler to be automatically supported by + * CppUnit, send a mail to the mailing list (preferred), or submit a feature request + * that indicates how to detect your compiler with the preprocessor (\#ifdef...) and + * your compiler location format. + */ + void setLocationFormat( const std::string &locationFormat ); + + /*! \brief Creates an instance of an outputter that matches your current compiler. + * \deprecated This class is specialized through parameterization instead of subclassing... + * Use CompilerOutputter::CompilerOutputter instead. + */ + static CompilerOutputter *defaultOutputter( TestResultCollector *result, + OStream &stream ); + + void write(); + + void setNoWrap(); + + void setWrapColumn( int wrapColumn ); + + int wrapColumn() const; + + virtual void printSuccess(); + virtual void printFailureReport(); + virtual void printFailuresList(); + virtual void printStatistics(); + virtual void printFailureDetail( TestFailure *failure ); + virtual void printFailureLocation( SourceLine sourceLine ); + virtual void printFailureType( TestFailure *failure ); + virtual void printFailedTestName( TestFailure *failure ); + virtual void printFailureMessage( TestFailure *failure ); + +private: + /// Prevents the use of the copy constructor. + CompilerOutputter( const CompilerOutputter © ); + + /// Prevents the use of the copy operator. + void operator =( const CompilerOutputter © ); + + virtual bool processLocationFormatCommand( char command, + const SourceLine &sourceLine ); + + virtual std::string extractBaseName( const std::string &fileName ) const; + +private: + TestResultCollector *m_result; + OStream &m_stream; + std::string m_locationFormat; + int m_wrapColumn; +}; + + +CPPUNIT_NS_END + + +#endif // CPPUNIT_COMPILERTESTRESULTOUTPUTTER_H diff --git a/3rdParty/include/cppunit/Exception.h b/3rdParty/include/cppunit/Exception.h new file mode 100644 index 0000000000..bf5fcacf66 --- /dev/null +++ b/3rdParty/include/cppunit/Exception.h @@ -0,0 +1,90 @@ +#ifndef CPPUNIT_EXCEPTION_H +#define CPPUNIT_EXCEPTION_H + +#include +#include +#include +#include + + +CPPUNIT_NS_BEGIN + + +/*! \brief Exceptions thrown by failed assertions. + * \ingroup BrowsingCollectedTestResult + * + * Exception is an exception that serves + * descriptive strings through its what() method + */ +class CPPUNIT_API Exception : public std::exception +{ +public: + /*! \brief Constructs the exception with the specified message and source location. + * \param message Message associated to the exception. + * \param sourceLine Source location related to the exception. + */ + Exception( const Message &message = Message(), + const SourceLine &sourceLine = SourceLine() ); + +#ifdef CPPUNIT_ENABLE_SOURCELINE_DEPRECATED + /*! + * \deprecated Use other constructor instead. + */ + Exception( std::string message, + long lineNumber, + std::string fileName ); +#endif + + /*! \brief Constructs a copy of an exception. + * \param other Exception to copy. + */ + Exception( const Exception &other ); + + /// Destructs the exception + virtual ~Exception() throw(); + + /// Performs an assignment + Exception &operator =( const Exception &other ); + + /// Returns descriptive message + const char *what() const throw(); + + /// Location where the error occured + SourceLine sourceLine() const; + + /// Message related to the exception. + Message message() const; + + /// Set the message. + void setMessage( const Message &message ); + +#ifdef CPPUNIT_ENABLE_SOURCELINE_DEPRECATED + /// The line on which the error occurred + long lineNumber() const; + + /// The file in which the error occurred + std::string fileName() const; + + static const std::string UNKNOWNFILENAME; + static const long UNKNOWNLINENUMBER; +#endif + + /// Clones the exception. + virtual Exception *clone() const; + +protected: + // VC++ does not recognize call to parent class when prefixed + // with a namespace. This is a workaround. + typedef std::exception SuperClass; + + Message m_message; + SourceLine m_sourceLine; + std::string m_whatMessage; +}; + + +CPPUNIT_NS_END + + +#endif // CPPUNIT_EXCEPTION_H + diff --git a/3rdParty/include/cppunit/Message.h b/3rdParty/include/cppunit/Message.h new file mode 100644 index 0000000000..1ae51cc2bd --- /dev/null +++ b/3rdParty/include/cppunit/Message.h @@ -0,0 +1,156 @@ +#ifndef CPPUNIT_MESSAGE_H +#define CPPUNIT_MESSAGE_H + +#include + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( push ) +#pragma warning( disable: 4251 ) // X needs to have dll-interface to be used by clients of class Z +#endif + +#include +#include + + +CPPUNIT_NS_BEGIN + + +#if CPPUNIT_NEED_DLL_DECL +// template class CPPUNIT_API std::deque; +#endif + +/*! \brief Message associated to an Exception. + * \ingroup CreatingNewAssertions + * A message is composed of two items: + * - a short description (~20/30 characters) + * - a list of detail strings + * + * The short description is used to indicate how the detail strings should be + * interpreted. It usually indicates the failure types, such as + * "assertion failed", "forced failure", "unexpected exception caught", + * "equality assertion failed"... It should not contains new line character (\n). + * + * Detail strings are used to provide more information about the failure. It + * can contains the asserted expression, the expected and actual values in an + * equality assertion, some addional messages... Detail strings can contains + * new line characters (\n). + */ +class CPPUNIT_API Message +{ +public: + Message(); + + // Ensure thread-safe copy by detaching the string. + Message( const Message &other ); + + explicit Message( const std::string &shortDescription ); + + Message( const std::string &shortDescription, + const std::string &detail1 ); + + Message( const std::string &shortDescription, + const std::string &detail1, + const std::string &detail2 ); + + Message( const std::string &shortDescription, + const std::string &detail1, + const std::string &detail2, + const std::string &detail3 ); + + Message &operator =( const Message &other ); + + /*! \brief Returns the short description. + * \return Short description. + */ + const std::string &shortDescription() const; + + /*! \brief Returns the number of detail string. + * \return Number of detail string. + */ + int detailCount() const; + + /*! \brief Returns the detail at the specified index. + * \param index Zero based index of the detail string to return. + * \returns Detail string at the specified index. + * \exception std::invalid_argument if \a index < 0 or index >= detailCount(). + */ + std::string detailAt( int index ) const; + + /*! \brief Returns a string that represents a list of the detail strings. + * + * Example: + * \code + * Message message( "not equal", "Expected: 3", "Actual: 7" ); + * std::string details = message.details(); + * // details contains: + * // "- Expected: 3\n- Actual: 7\n" \endcode + * + * \return A string that is a concatenation of all the detail strings. Each detail + * string is prefixed with '- ' and suffixed with '\n' before being + * concatenated to the other. + */ + std::string details() const; + + /*! \brief Removes all detail strings. + */ + void clearDetails(); + + /*! \brief Adds a single detail string. + * \param detail Detail string to add. + */ + void addDetail( const std::string &detail ); + + /*! \brief Adds two detail strings. + * \param detail1 Detail string to add. + * \param detail2 Detail string to add. + */ + void addDetail( const std::string &detail1, + const std::string &detail2 ); + + /*! \brief Adds three detail strings. + * \param detail1 Detail string to add. + * \param detail2 Detail string to add. + * \param detail3 Detail string to add. + */ + void addDetail( const std::string &detail1, + const std::string &detail2, + const std::string &detail3 ); + + /*! \brief Adds the detail strings of the specified message. + * \param message All the detail strings of this message are added to this one. + */ + void addDetail( const Message &message ); + + /*! \brief Sets the short description. + * \param shortDescription New short description. + */ + void setShortDescription( const std::string &shortDescription ); + + /*! \brief Tests if a message is identical to another one. + * \param other Message this message is compared to. + * \return \c true if the two message are identical, \c false otherwise. + */ + bool operator ==( const Message &other ) const; + + /*! \brief Tests if a message is different from another one. + * \param other Message this message is compared to. + * \return \c true if the two message are not identical, \c false otherwise. + */ + bool operator !=( const Message &other ) const; + +private: + std::string m_shortDescription; + + typedef CppUnitDeque Details; + Details m_details; +}; + + +CPPUNIT_NS_END + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( pop ) +#endif + + +#endif // CPPUNIT_MESSAGE_H diff --git a/3rdParty/include/cppunit/Outputter.h b/3rdParty/include/cppunit/Outputter.h new file mode 100644 index 0000000000..f31d6815dc --- /dev/null +++ b/3rdParty/include/cppunit/Outputter.h @@ -0,0 +1,26 @@ +#ifndef CPPUNIT_OUTPUTTER_H +#define CPPUNIT_OUTPUTTER_H + +#include + + +CPPUNIT_NS_BEGIN + + +/*! \brief Abstract outputter to print test result summary. + * \ingroup WritingTestResult + */ +class CPPUNIT_API Outputter +{ +public: + /// Destructor. + virtual ~Outputter() {} + + virtual void write() =0; +}; + + +CPPUNIT_NS_END + + +#endif // CPPUNIT_OUTPUTTER_H diff --git a/3rdParty/include/cppunit/Portability.h b/3rdParty/include/cppunit/Portability.h new file mode 100644 index 0000000000..ddf0316918 --- /dev/null +++ b/3rdParty/include/cppunit/Portability.h @@ -0,0 +1,177 @@ +#ifndef CPPUNIT_PORTABILITY_H +#define CPPUNIT_PORTABILITY_H + +#if defined(_WIN32) && !defined(WIN32) +# define WIN32 1 +#endif + +/* include platform specific config */ +#if defined(__BORLANDC__) +# include +#elif defined (_MSC_VER) +# if _MSC_VER == 1200 && defined(_WIN32_WCE) //evc4 +# include +# else +# include +# endif +#else +# include +#endif + +// Version number of package +#ifndef CPPUNIT_VERSION +#define CPPUNIT_VERSION "1.12.0" +#endif + +#include // define CPPUNIT_API & CPPUNIT_NEED_DLL_DECL +#include + + +/* Options that the library user may switch on or off. + * If the user has not done so, we chose default values. + */ + + +/* Define to 1 if you wish to have the old-style macros + assert(), assertEqual(), assertDoublesEqual(), and assertLongsEqual() */ +#if !defined(CPPUNIT_ENABLE_NAKED_ASSERT) +# define CPPUNIT_ENABLE_NAKED_ASSERT 0 +#endif + +/* Define to 1 if you wish to have the old-style CU_TEST family + of macros. */ +#if !defined(CPPUNIT_ENABLE_CU_TEST_MACROS) +# define CPPUNIT_ENABLE_CU_TEST_MACROS 0 +#endif + +/* Define to 1 if the preprocessor expands (#foo) to "foo" (quotes incl.) + I don't think there is any C preprocess that does NOT support this! */ +#if !defined(CPPUNIT_HAVE_CPP_SOURCE_ANNOTATION) +# define CPPUNIT_HAVE_CPP_SOURCE_ANNOTATION 1 +#endif + +/* Assumes that STL and CppUnit are in global space if the compiler does not + support namespace. */ +#if !defined(CPPUNIT_HAVE_NAMESPACES) +# if !defined(CPPUNIT_NO_NAMESPACE) +# define CPPUNIT_NO_NAMESPACE 1 +# endif // !defined(CPPUNIT_NO_NAMESPACE) +# if !defined(CPPUNIT_NO_STD_NAMESPACE) +# define CPPUNIT_NO_STD_NAMESPACE 1 +# endif // !defined(CPPUNIT_NO_STD_NAMESPACE) +#endif // !defined(CPPUNIT_HAVE_NAMESPACES) + +/* Define CPPUNIT_STD_NEED_ALLOCATOR to 1 if you need to specify + * the allocator you used when instantiating STL container. Typically + * used for compilers that do not support template default parameter. + * CPPUNIT_STD_ALLOCATOR will be used as the allocator. Default is + * std::allocator. On some compilers, you may need to change this to + * std::allocator. + */ +#if CPPUNIT_STD_NEED_ALLOCATOR +# if !defined(CPPUNIT_STD_ALLOCATOR) +# define CPPUNIT_STD_ALLOCATOR std::allocator +# endif // !defined(CPPUNIT_STD_ALLOCATOR) +#endif // defined(CPPUNIT_STD_NEED_ALLOCATOR) + + +// Compiler error location format for CompilerOutputter +// If not define, assumes that it's gcc +// See class CompilerOutputter for format. +#if !defined(CPPUNIT_COMPILER_LOCATION_FORMAT) +#if defined(__GNUC__) && ( defined(__APPLE_CPP__) || defined(__APPLE_CC__) ) +// gcc/Xcode integration on Mac OS X +# define CPPUNIT_COMPILER_LOCATION_FORMAT "%p:%l: " +#else +# define CPPUNIT_COMPILER_LOCATION_FORMAT "%f:%l:" +#endif +#endif + +// If CPPUNIT_HAVE_CPP_CAST is defined, then c++ style cast will be used, +// otherwise, C style cast are used. +#if defined( CPPUNIT_HAVE_CPP_CAST ) +# define CPPUNIT_CONST_CAST( TargetType, pointer ) \ + const_cast( pointer ) + +# define CPPUNIT_STATIC_CAST( TargetType, pointer ) \ + static_cast( pointer ) +#else // defined( CPPUNIT_HAVE_CPP_CAST ) +# define CPPUNIT_CONST_CAST( TargetType, pointer ) \ + ((TargetType)( pointer )) +# define CPPUNIT_STATIC_CAST( TargetType, pointer ) \ + ((TargetType)( pointer )) +#endif // defined( CPPUNIT_HAVE_CPP_CAST ) + +// If CPPUNIT_NO_STD_NAMESPACE is defined then STL are in the global space. +// => Define macro 'std' to nothing +#if defined(CPPUNIT_NO_STD_NAMESPACE) +# undef std +# define std +#endif // defined(CPPUNIT_NO_STD_NAMESPACE) + +// If CPPUNIT_NO_NAMESPACE is defined, then put CppUnit classes in the +// global namespace: the compiler does not support namespace. +#if defined(CPPUNIT_NO_NAMESPACE) +# define CPPUNIT_NS_BEGIN +# define CPPUNIT_NS_END +# define CPPUNIT_NS +#else // defined(CPPUNIT_NO_NAMESPACE) +# define CPPUNIT_NS_BEGIN namespace CppUnit { +# define CPPUNIT_NS_END } +# define CPPUNIT_NS CppUnit +#endif // defined(CPPUNIT_NO_NAMESPACE) + +/*! Stringize a symbol. + * + * Use this macro to convert a preprocessor symbol to a string. + * + * Example of usage: + * \code + * #define CPPUNIT_PLUGIN_EXPORTED_NAME cppunitTestPlugIn + * const char *name = CPPUNIT_STRINGIZE( CPPUNIT_PLUGIN_EXPORTED_NAME ); + * \endcode + */ +#define CPPUNIT_STRINGIZE( symbol ) _CPPUNIT_DO_STRINGIZE( symbol ) + +/// \internal +#define _CPPUNIT_DO_STRINGIZE( symbol ) #symbol + +/*! Joins to symbol after expanding them into string. + * + * Use this macro to join two symbols. Example of usage: + * + * \code + * #define MAKE_UNIQUE_NAME(prefix) CPPUNIT_JOIN( prefix, __LINE__ ) + * \endcode + * + * The macro defined in the example concatenate a given prefix with the line number + * to obtain a 'unique' identifier. + * + * \internal From boost documentation: + * The following piece of macro magic joins the two + * arguments together, even when one of the arguments is + * itself a macro (see 16.3.1 in C++ standard). The key + * is that macro expansion of macro arguments does not + * occur in CPPUNIT_JOIN2 but does in CPPUNIT_JOIN. + */ +#define CPPUNIT_JOIN( symbol1, symbol2 ) _CPPUNIT_DO_JOIN( symbol1, symbol2 ) + +/// \internal +#define _CPPUNIT_DO_JOIN( symbol1, symbol2 ) _CPPUNIT_DO_JOIN2( symbol1, symbol2 ) + +/// \internal +#define _CPPUNIT_DO_JOIN2( symbol1, symbol2 ) symbol1##symbol2 + +/*! Adds the line number to the specified string to create a unique identifier. + * \param prefix Prefix added to the line number to create a unique identifier. + * \see CPPUNIT_TEST_SUITE_REGISTRATION for an example of usage. + */ +#define CPPUNIT_MAKE_UNIQUE_NAME( prefix ) CPPUNIT_JOIN( prefix, __LINE__ ) + +/*! Defines wrap colunm for %CppUnit. Used by CompilerOuputter. + */ +#if !defined(CPPUNIT_WRAP_COLUMN) +# define CPPUNIT_WRAP_COLUMN 79 +#endif + +#endif // CPPUNIT_PORTABILITY_H diff --git a/3rdParty/include/cppunit/Protector.h b/3rdParty/include/cppunit/Protector.h new file mode 100644 index 0000000000..d14e75f641 --- /dev/null +++ b/3rdParty/include/cppunit/Protector.h @@ -0,0 +1,94 @@ +#ifndef CPPUNIT_PROTECTOR_H +#define CPPUNIT_PROTECTOR_H + +#include + +CPPUNIT_NS_BEGIN + +class Exception; +class Message; +class ProtectorContext; +class TestResult; + + +class CPPUNIT_API Functor +{ +public: + virtual ~Functor(); + + virtual bool operator()() const =0; +}; + + +/*! \brief Protects one or more test case run. + * + * Protector are used to globably 'decorate' a test case. The most common + * usage of Protector is to catch exception that do not subclass std::exception, + * such as MFC CException class or Rogue Wave RWXMsg class, and capture the + * message associated to the exception. In fact, CppUnit capture message from + * Exception and std::exception using a Protector. + * + * Protector are chained. When you add a Protector using + * TestResult::pushProtector(), your protector is in fact passed as a Functor + * to the first protector of the chain. + * + * TestCase protects call to setUp(), runTest() and tearDown() by calling + * TestResult::protect(). + * + * Because the protector chain is handled by TestResult, a protector can be + * active for a single test, or a complete test run. + * + * Here are some possible usages: + * - run all test case in a separate thread and assumes the test failed if it + * did not finish in a given time (infinite loop work around) + * - performance tracing : time only the runTest() time. + * \sa TestResult, TestCase, TestListener. + */ +class CPPUNIT_API Protector +{ +public: + virtual ~Protector(); + + virtual bool protect( const Functor &functor, + const ProtectorContext &context ) =0; + +protected: + void reportError( const ProtectorContext &context, + const Exception &error ) const; + + void reportError( const ProtectorContext &context, + const Message &message, + const SourceLine &sourceLine = SourceLine() ) const; + + void reportFailure( const ProtectorContext &context, + const Exception &failure ) const; + + Message actualMessage( const Message &message, + const ProtectorContext &context ) const; +}; + + +/*! \brief Scoped protector push to TestResult. + * + * Adds the specified Protector to the specified TestResult for the object + * life-time. + */ +class CPPUNIT_API ProtectorGuard +{ +public: + /// Pushes the specified protector. + ProtectorGuard( TestResult *result, + Protector *protector ); + + /// Pops the protector. + ~ProtectorGuard(); + +private: + TestResult *m_result; +}; + +CPPUNIT_NS_END + + +#endif // CPPUNIT_PROTECTOR_H + diff --git a/3rdParty/include/cppunit/SourceLine.h b/3rdParty/include/cppunit/SourceLine.h new file mode 100644 index 0000000000..f7a85df7f0 --- /dev/null +++ b/3rdParty/include/cppunit/SourceLine.h @@ -0,0 +1,63 @@ +#ifndef CPPUNIT_SOURCELINE_H +#define CPPUNIT_SOURCELINE_H + +#include +#include + +/*! \brief Constructs a SourceLine object initialized with the location where the macro is expanded. + * \ingroup CreatingNewAssertions + * \relates CppUnit::SourceLine + * Used to write your own assertion macros. + * \see Asserter for example of usage. + */ +#define CPPUNIT_SOURCELINE() CPPUNIT_NS::SourceLine( __FILE__, __LINE__ ) + + +CPPUNIT_NS_BEGIN + + +/*! \brief Represents a source line location. + * \ingroup CreatingNewAssertions + * \ingroup BrowsingCollectedTestResult + * + * Used to capture the failure location in assertion. + * + * Use the CPPUNIT_SOURCELINE() macro to construct that object. Typically used when + * writing an assertion macro in association with Asserter. + * + * \see Asserter. + */ +class CPPUNIT_API SourceLine +{ +public: + SourceLine(); + + // Ensure thread-safe copy by detaching the string buffer. + SourceLine( const SourceLine &other ); + + SourceLine( const std::string &fileName, + int lineNumber ); + + SourceLine &operator =( const SourceLine &other ); + + /// Destructor. + virtual ~SourceLine(); + + bool isValid() const; + + int lineNumber() const; + + std::string fileName() const; + + bool operator ==( const SourceLine &other ) const; + bool operator !=( const SourceLine &other ) const; + +private: + std::string m_fileName; + int m_lineNumber; +}; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_SOURCELINE_H diff --git a/3rdParty/include/cppunit/SynchronizedObject.h b/3rdParty/include/cppunit/SynchronizedObject.h new file mode 100644 index 0000000000..0f7d094aa5 --- /dev/null +++ b/3rdParty/include/cppunit/SynchronizedObject.h @@ -0,0 +1,80 @@ +#ifndef CPPUNIT_SYNCHRONIZEDOBJECT_H +#define CPPUNIT_SYNCHRONIZEDOBJECT_H + +#include + + +CPPUNIT_NS_BEGIN + + +/*! \brief Base class for synchronized object. + * + * Synchronized object are object which members are used concurrently by mutiple + * threads. + * + * This class define the class SynchronizationObject which must be subclassed + * to implement an actual lock. + * + * Each instance of this class holds a pointer on a lock object. + * + * See src/msvc6/MfcSynchronizedObject.h for an example. + */ +class CPPUNIT_API SynchronizedObject +{ +public: + /*! \brief Abstract synchronization object (mutex) + */ + class SynchronizationObject + { + public: + SynchronizationObject() {} + virtual ~SynchronizationObject() {} + + virtual void lock() {} + virtual void unlock() {} + }; + + /*! Constructs a SynchronizedObject object. + */ + SynchronizedObject( SynchronizationObject *syncObject =0 ); + + /// Destructor. + virtual ~SynchronizedObject(); + +protected: + /*! \brief Locks a synchronization object in the current scope. + */ + class ExclusiveZone + { + SynchronizationObject *m_syncObject; + + public: + ExclusiveZone( SynchronizationObject *syncObject ) + : m_syncObject( syncObject ) + { + m_syncObject->lock(); + } + + ~ExclusiveZone() + { + m_syncObject->unlock (); + } + }; + + virtual void setSynchronizationObject( SynchronizationObject *syncObject ); + +protected: + SynchronizationObject *m_syncObject; + +private: + /// Prevents the use of the copy constructor. + SynchronizedObject( const SynchronizedObject © ); + + /// Prevents the use of the copy operator. + void operator =( const SynchronizedObject © ); +}; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_SYNCHRONIZEDOBJECT_H diff --git a/3rdParty/include/cppunit/Test.h b/3rdParty/include/cppunit/Test.h new file mode 100644 index 0000000000..a56be0fbea --- /dev/null +++ b/3rdParty/include/cppunit/Test.h @@ -0,0 +1,117 @@ +#ifndef CPPUNIT_TEST_H +#define CPPUNIT_TEST_H + +#include +#include + +CPPUNIT_NS_BEGIN + + +class TestResult; +class TestPath; + +/*! \brief Base class for all test objects. + * \ingroup BrowsingCollectedTestResult + * + * All test objects should be a subclass of Test. Some test objects, + * TestCase for example, represent one individual test. Other test + * objects, such as TestSuite, are comprised of several tests. + * + * When a Test is run, the result is collected by a TestResult object. + * + * \see TestCase + * \see TestSuite + */ +class CPPUNIT_API Test +{ +public: + virtual ~Test() {}; + + /*! \brief Run the test, collecting results. + */ + virtual void run( TestResult *result ) =0; + + /*! \brief Return the number of test cases invoked by run(). + * + * The base unit of testing is the class TestCase. This + * method returns the number of TestCase objects invoked by + * the run() method. + */ + virtual int countTestCases () const =0; + + /*! \brief Returns the number of direct child of the test. + */ + virtual int getChildTestCount() const =0; + + /*! \brief Returns the child test of the specified index. + * + * This method test if the index is valid, then call doGetChildTestAt() if + * the index is valid. Otherwise std::out_of_range exception is thrown. + * + * You should override doGetChildTestAt() method. + * + * \param index Zero based index of the child test to return. + * \return Pointer on the test. Never \c NULL. + * \exception std::out_of_range is \a index is < 0 or >= getChildTestCount(). + */ + virtual Test *getChildTestAt( int index ) const; + + /*! \brief Returns the test name. + * + * Each test has a name. This name may be used to find the + * test in a suite or registry of tests. + */ + virtual std::string getName () const =0; + + /*! \brief Finds the test with the specified name and its parents test. + * \param testName Name of the test to find. + * \param testPath If the test is found, then all the tests traversed to access + * \a test are added to \a testPath, including \c this and \a test. + * \return \c true if a test with the specified name is found, \c false otherwise. + */ + virtual bool findTestPath( const std::string &testName, + TestPath &testPath ) const; + + /*! \brief Finds the specified test and its parents test. + * \param test Test to find. + * \param testPath If the test is found, then all the tests traversed to access + * \a test are added to \a testPath, including \c this and \a test. + * \return \c true if the specified test is found, \c false otherwise. + */ + virtual bool findTestPath( const Test *test, + TestPath &testPath ) const; + + /*! \brief Finds the test with the specified name in the hierarchy. + * \param testName Name of the test to find. + * \return Pointer on the first test found that is named \a testName. Never \c NULL. + * \exception std::invalid_argument if no test named \a testName is found. + */ + virtual Test *findTest( const std::string &testName ) const; + + /*! \brief Resolved the specified test path with this test acting as 'root'. + * \param testPath Test path string to resolve. + * \return Resolved TestPath. + * \exception std::invalid_argument if \a testPath could not be resolved. + * \see TestPath. + */ + virtual TestPath resolveTestPath( const std::string &testPath ) const; + +protected: + /*! Throws an exception if the specified index is invalid. + * \param index Zero base index of a child test. + * \exception std::out_of_range is \a index is < 0 or >= getChildTestCount(). + */ + virtual void checkIsValidIndex( int index ) const; + + /*! \brief Returns the child test of the specified valid index. + * \param index Zero based valid index of the child test to return. + * \return Pointer on the test. Never \c NULL. + */ + virtual Test *doGetChildTestAt( int index ) const =0; +}; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_TEST_H + diff --git a/3rdParty/include/cppunit/TestAssert.h b/3rdParty/include/cppunit/TestAssert.h new file mode 100644 index 0000000000..f74797b9da --- /dev/null +++ b/3rdParty/include/cppunit/TestAssert.h @@ -0,0 +1,428 @@ +#ifndef CPPUNIT_TESTASSERT_H +#define CPPUNIT_TESTASSERT_H + +#include +#include +#include +#include +#include +#include // For struct assertion_traits + + +CPPUNIT_NS_BEGIN + + +/*! \brief Traits used by CPPUNIT_ASSERT_EQUAL(). + * + * Here is an example of specialising these traits: + * + * \code + * template<> + * struct assertion_traits // specialization for the std::string type + * { + * static bool equal( const std::string& x, const std::string& y ) + * { + * return x == y; + * } + * + * static std::string toString( const std::string& x ) + * { + * std::string text = '"' + x + '"'; // adds quote around the string to see whitespace + * OStringStream ost; + * ost << text; + * return ost.str(); + * } + * }; + * \endcode + */ +template +struct assertion_traits +{ + static bool equal( const T& x, const T& y ) + { + return x == y; + } + + static std::string toString( const T& x ) + { + OStringStream ost; + ost << x; + return ost.str(); + } +}; + + +/*! \brief Traits used by CPPUNIT_ASSERT_DOUBLES_EQUAL(). + * + * This specialisation from @c struct @c assertion_traits<> ensures that + * doubles are converted in full, instead of being rounded to the default + * 6 digits of precision. Use the system defined ISO C99 macro DBL_DIG + * within float.h is available to define the maximum precision, otherwise + * use the hard-coded maximum precision of 15. + */ +template <> +struct assertion_traits +{ + static bool equal( double x, double y ) + { + return x == y; + } + + static std::string toString( double x ) + { +#ifdef DBL_DIG + const int precision = DBL_DIG; +#else + const int precision = 15; +#endif // #ifdef DBL_DIG + char buffer[128]; +#ifdef __STDC_SECURE_LIB__ // Use secure version with visual studio 2005 to avoid warning. + sprintf_s(buffer, sizeof(buffer), "%.*g", precision, x); +#else + sprintf(buffer, "%.*g", precision, x); +#endif + return buffer; + } +}; + + +/*! \brief (Implementation) Asserts that two objects of the same type are equals. + * Use CPPUNIT_ASSERT_EQUAL instead of this function. + * \sa assertion_traits, Asserter::failNotEqual(). + */ +template +void assertEquals( const T& expected, + const T& actual, + SourceLine sourceLine, + const std::string &message ) +{ + if ( !assertion_traits::equal(expected,actual) ) // lazy toString conversion... + { + Asserter::failNotEqual( assertion_traits::toString(expected), + assertion_traits::toString(actual), + sourceLine, + message ); + } +} + + +/*! \brief (Implementation) Asserts that two double are equals given a tolerance. + * Use CPPUNIT_ASSERT_DOUBLES_EQUAL instead of this function. + * \sa Asserter::failNotEqual(). + * \sa CPPUNIT_ASSERT_DOUBLES_EQUAL for detailed semantic of the assertion. + */ +void CPPUNIT_API assertDoubleEquals( double expected, + double actual, + double delta, + SourceLine sourceLine, + const std::string &message ); + + +/* A set of macros which allow us to get the line number + * and file name at the point of an error. + * Just goes to show that preprocessors do have some + * redeeming qualities. + */ +#if CPPUNIT_HAVE_CPP_SOURCE_ANNOTATION +/** Assertions that a condition is \c true. + * \ingroup Assertions + */ +#define CPPUNIT_ASSERT(condition) \ + ( CPPUNIT_NS::Asserter::failIf( !(condition), \ + CPPUNIT_NS::Message( "assertion failed", \ + "Expression: " #condition), \ + CPPUNIT_SOURCELINE() ) ) +#else +#define CPPUNIT_ASSERT(condition) \ + ( CPPUNIT_NS::Asserter::failIf( !(condition), \ + CPPUNIT_NS::Message( "assertion failed" ), \ + CPPUNIT_SOURCELINE() ) ) +#endif + +/** Assertion with a user specified message. + * \ingroup Assertions + * \param message Message reported in diagnostic if \a condition evaluates + * to \c false. + * \param condition If this condition evaluates to \c false then the + * test failed. + */ +#define CPPUNIT_ASSERT_MESSAGE(message,condition) \ + ( CPPUNIT_NS::Asserter::failIf( !(condition), \ + CPPUNIT_NS::Message( "assertion failed", \ + "Expression: " \ + #condition, \ + message ), \ + CPPUNIT_SOURCELINE() ) ) + +/** Fails with the specified message. + * \ingroup Assertions + * \param message Message reported in diagnostic. + */ +#define CPPUNIT_FAIL( message ) \ + ( CPPUNIT_NS::Asserter::fail( CPPUNIT_NS::Message( "forced failure", \ + message ), \ + CPPUNIT_SOURCELINE() ) ) + +#ifdef CPPUNIT_ENABLE_SOURCELINE_DEPRECATED +/// Generalized macro for primitive value comparisons +#define CPPUNIT_ASSERT_EQUAL(expected,actual) \ + ( CPPUNIT_NS::assertEquals( (expected), \ + (actual), \ + __LINE__, __FILE__ ) ) +#else +/** Asserts that two values are equals. + * \ingroup Assertions + * + * Equality and string representation can be defined with + * an appropriate CppUnit::assertion_traits class. + * + * A diagnostic is printed if actual and expected values disagree. + * + * Requirement for \a expected and \a actual parameters: + * - They are exactly of the same type + * - They are serializable into a std::strstream using operator <<. + * - They can be compared using operator ==. + * + * The last two requirements (serialization and comparison) can be + * removed by specializing the CppUnit::assertion_traits. + */ +#define CPPUNIT_ASSERT_EQUAL(expected,actual) \ + ( CPPUNIT_NS::assertEquals( (expected), \ + (actual), \ + CPPUNIT_SOURCELINE(), \ + "" ) ) + +/** Asserts that two values are equals, provides additional message on failure. + * \ingroup Assertions + * + * Equality and string representation can be defined with + * an appropriate assertion_traits class. + * + * A diagnostic is printed if actual and expected values disagree. + * The message is printed in addition to the expected and actual value + * to provide additional information. + * + * Requirement for \a expected and \a actual parameters: + * - They are exactly of the same type + * - They are serializable into a std::strstream using operator <<. + * - They can be compared using operator ==. + * + * The last two requirements (serialization and comparison) can be + * removed by specializing the CppUnit::assertion_traits. + */ +#define CPPUNIT_ASSERT_EQUAL_MESSAGE(message,expected,actual) \ + ( CPPUNIT_NS::assertEquals( (expected), \ + (actual), \ + CPPUNIT_SOURCELINE(), \ + (message) ) ) +#endif + +/*! \brief Macro for primitive double value comparisons. + * \ingroup Assertions + * + * The assertion pass if both expected and actual are finite and + * \c fabs( \c expected - \c actual ) <= \c delta. + * If either \c expected or actual are infinite (+/- inf), the + * assertion pass if \c expected == \c actual. + * If either \c expected or \c actual is a NaN (not a number), then + * the assertion fails. + */ +#define CPPUNIT_ASSERT_DOUBLES_EQUAL(expected,actual,delta) \ + ( CPPUNIT_NS::assertDoubleEquals( (expected), \ + (actual), \ + (delta), \ + CPPUNIT_SOURCELINE(), \ + "" ) ) + + +/*! \brief Macro for primitive double value comparisons, setting a + * user-supplied message in case of failure. + * \ingroup Assertions + * \sa CPPUNIT_ASSERT_DOUBLES_EQUAL for detailed semantic of the assertion. + */ +#define CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(message,expected,actual,delta) \ + ( CPPUNIT_NS::assertDoubleEquals( (expected), \ + (actual), \ + (delta), \ + CPPUNIT_SOURCELINE(), \ + (message) ) ) + + +/** Asserts that the given expression throws an exception of the specified type. + * \ingroup Assertions + * Example of usage: + * \code + * std::vector v; + * CPPUNIT_ASSERT_THROW( v.at( 50 ), std::out_of_range ); + * \endcode + */ +# define CPPUNIT_ASSERT_THROW( expression, ExceptionType ) \ + CPPUNIT_ASSERT_THROW_MESSAGE( CPPUNIT_NS::AdditionalMessage(), \ + expression, \ + ExceptionType ) + + +// implementation detail +#if CPPUNIT_USE_TYPEINFO_NAME +#define CPPUNIT_EXTRACT_EXCEPTION_TYPE_( exception, no_rtti_message ) \ + CPPUNIT_NS::TypeInfoHelper::getClassName( typeid(exception) ) +#else +#define CPPUNIT_EXTRACT_EXCEPTION_TYPE_( exception, no_rtti_message ) \ + std::string( no_rtti_message ) +#endif // CPPUNIT_USE_TYPEINFO_NAME + +// implementation detail +#define CPPUNIT_GET_PARAMETER_STRING( parameter ) #parameter + +/** Asserts that the given expression throws an exception of the specified type, + * setting a user supplied message in case of failure. + * \ingroup Assertions + * Example of usage: + * \code + * std::vector v; + * CPPUNIT_ASSERT_THROW_MESSAGE( "- std::vector v;", v.at( 50 ), std::out_of_range ); + * \endcode + */ +# define CPPUNIT_ASSERT_THROW_MESSAGE( message, expression, ExceptionType ) \ + do { \ + bool cpputCorrectExceptionThrown_ = false; \ + CPPUNIT_NS::Message cpputMsg_( "expected exception not thrown" ); \ + cpputMsg_.addDetail( message ); \ + cpputMsg_.addDetail( "Expected: " \ + CPPUNIT_GET_PARAMETER_STRING( ExceptionType ) ); \ + \ + try { \ + expression; \ + } catch ( const ExceptionType & ) { \ + cpputCorrectExceptionThrown_ = true; \ + } catch ( const std::exception &e) { \ + cpputMsg_.addDetail( "Actual : " + \ + CPPUNIT_EXTRACT_EXCEPTION_TYPE_( e, \ + "std::exception or derived") ); \ + cpputMsg_.addDetail( std::string("What() : ") + e.what() ); \ + } catch ( ... ) { \ + cpputMsg_.addDetail( "Actual : unknown."); \ + } \ + \ + if ( cpputCorrectExceptionThrown_ ) \ + break; \ + \ + CPPUNIT_NS::Asserter::fail( cpputMsg_, \ + CPPUNIT_SOURCELINE() ); \ + } while ( false ) + + +/** Asserts that the given expression does not throw any exceptions. + * \ingroup Assertions + * Example of usage: + * \code + * std::vector v; + * v.push_back( 10 ); + * CPPUNIT_ASSERT_NO_THROW( v.at( 0 ) ); + * \endcode + */ +# define CPPUNIT_ASSERT_NO_THROW( expression ) \ + CPPUNIT_ASSERT_NO_THROW_MESSAGE( CPPUNIT_NS::AdditionalMessage(), \ + expression ) + + +/** Asserts that the given expression does not throw any exceptions, + * setting a user supplied message in case of failure. + * \ingroup Assertions + * Example of usage: + * \code + * std::vector v; + * v.push_back( 10 ); + * CPPUNIT_ASSERT_NO_THROW( "std::vector v;", v.at( 0 ) ); + * \endcode + */ +# define CPPUNIT_ASSERT_NO_THROW_MESSAGE( message, expression ) \ + do { \ + CPPUNIT_NS::Message cpputMsg_( "unexpected exception caught" ); \ + cpputMsg_.addDetail( message ); \ + \ + try { \ + expression; \ + } catch ( const std::exception &e ) { \ + cpputMsg_.addDetail( "Caught: " + \ + CPPUNIT_EXTRACT_EXCEPTION_TYPE_( e, \ + "std::exception or derived" ) ); \ + cpputMsg_.addDetail( std::string("What(): ") + e.what() ); \ + CPPUNIT_NS::Asserter::fail( cpputMsg_, \ + CPPUNIT_SOURCELINE() ); \ + } catch ( ... ) { \ + cpputMsg_.addDetail( "Caught: unknown." ); \ + CPPUNIT_NS::Asserter::fail( cpputMsg_, \ + CPPUNIT_SOURCELINE() ); \ + } \ + } while ( false ) + + +/** Asserts that an assertion fail. + * \ingroup Assertions + * Use to test assertions. + * Example of usage: + * \code + * CPPUNIT_ASSERT_ASSERTION_FAIL( CPPUNIT_ASSERT( 1 == 2 ) ); + * \endcode + */ +# define CPPUNIT_ASSERT_ASSERTION_FAIL( assertion ) \ + CPPUNIT_ASSERT_THROW( assertion, CPPUNIT_NS::Exception ) + + +/** Asserts that an assertion fail, with a user-supplied message in + * case of error. + * \ingroup Assertions + * Use to test assertions. + * Example of usage: + * \code + * CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE( "1 == 2", CPPUNIT_ASSERT( 1 == 2 ) ); + * \endcode + */ +# define CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE( message, assertion ) \ + CPPUNIT_ASSERT_THROW_MESSAGE( message, assertion, CPPUNIT_NS::Exception ) + + +/** Asserts that an assertion pass. + * \ingroup Assertions + * Use to test assertions. + * Example of usage: + * \code + * CPPUNIT_ASSERT_ASSERTION_PASS( CPPUNIT_ASSERT( 1 == 1 ) ); + * \endcode + */ +# define CPPUNIT_ASSERT_ASSERTION_PASS( assertion ) \ + CPPUNIT_ASSERT_NO_THROW( assertion ) + + +/** Asserts that an assertion pass, with a user-supplied message in + * case of failure. + * \ingroup Assertions + * Use to test assertions. + * Example of usage: + * \code + * CPPUNIT_ASSERT_ASSERTION_PASS_MESSAGE( "1 != 1", CPPUNIT_ASSERT( 1 == 1 ) ); + * \endcode + */ +# define CPPUNIT_ASSERT_ASSERTION_PASS_MESSAGE( message, assertion ) \ + CPPUNIT_ASSERT_NO_THROW_MESSAGE( message, assertion ) + + + + +// Backwards compatibility + +#if CPPUNIT_ENABLE_NAKED_ASSERT + +#undef assert +#define assert(c) CPPUNIT_ASSERT(c) +#define assertEqual(e,a) CPPUNIT_ASSERT_EQUAL(e,a) +#define assertDoublesEqual(e,a,d) CPPUNIT_ASSERT_DOUBLES_EQUAL(e,a,d) +#define assertLongsEqual(e,a) CPPUNIT_ASSERT_EQUAL(e,a) + +#endif + + +CPPUNIT_NS_END + +#endif // CPPUNIT_TESTASSERT_H diff --git a/3rdParty/include/cppunit/TestCaller.h b/3rdParty/include/cppunit/TestCaller.h new file mode 100644 index 0000000000..dc4d82ed9b --- /dev/null +++ b/3rdParty/include/cppunit/TestCaller.h @@ -0,0 +1,204 @@ +#ifndef CPPUNIT_TESTCALLER_H // -*- C++ -*- +#define CPPUNIT_TESTCALLER_H + +#include +#include + + +#if CPPUNIT_USE_TYPEINFO_NAME +# include +#endif + + +CPPUNIT_NS_BEGIN + +#if 0 +/*! \brief Marker class indicating that no exception is expected by TestCaller. + * This class is an implementation detail. You should never use this class directly. + */ +class CPPUNIT_API NoExceptionExpected +{ +private: + //! Prevent class instantiation. + NoExceptionExpected(); +}; + + +/*! \brief (Implementation) Traits used by TestCaller to expect an exception. + * + * This class is an implementation detail. You should never use this class directly. + */ +template +struct ExpectedExceptionTraits +{ + static void expectedException() + { +#if CPPUNIT_USE_TYPEINFO_NAME + throw Exception( Message( + "expected exception not thrown", + "Expected exception type: " + + TypeInfoHelper::getClassName( typeid( ExceptionType ) ) ) ); +#else + throw Exception( "expected exception not thrown" ); +#endif + } +}; + + +/*! \brief (Implementation) Traits specialization used by TestCaller to + * expect no exception. + * + * This class is an implementation detail. You should never use this class directly. + */ +template<> +struct ExpectedExceptionTraits +{ + static void expectedException() + { + } +}; + + +#endif + +//*** FIXME: rework this when class Fixture is implemented. ***// + + +/*! \brief Generate a test case from a fixture method. + * \ingroup WritingTestFixture + * + * A test caller provides access to a test case method + * on a test fixture class. Test callers are useful when + * you want to run an individual test or add it to a + * suite. + * Test Callers invoke only one Test (i.e. test method) on one + * Fixture of a TestFixture. + * + * Here is an example: + * \code + * class MathTest : public CppUnit::TestFixture { + * ... + * public: + * void setUp(); + * void tearDown(); + * + * void testAdd(); + * void testSubtract(); + * }; + * + * CppUnit::Test *MathTest::suite() { + * CppUnit::TestSuite *suite = new CppUnit::TestSuite; + * + * suite->addTest( new CppUnit::TestCaller( "testAdd", testAdd ) ); + * return suite; + * } + * \endcode + * + * You can use a TestCaller to bind any test method on a TestFixture + * class, as long as it accepts void and returns void. + * + * \see TestCase + */ + +template +class TestCaller : public TestCase +{ + typedef void (Fixture::*TestMethod)(); + +public: + /*! + * Constructor for TestCaller. This constructor builds a new Fixture + * instance owned by the TestCaller. + * \param name name of this TestCaller + * \param test the method this TestCaller calls in runTest() + */ + TestCaller( std::string name, TestMethod test ) : + TestCase( name ), + m_ownFixture( true ), + m_fixture( new Fixture() ), + m_test( test ) + { + } + + /*! + * Constructor for TestCaller. + * This constructor does not create a new Fixture instance but accepts + * an existing one as parameter. The TestCaller will not own the + * Fixture object. + * \param name name of this TestCaller + * \param test the method this TestCaller calls in runTest() + * \param fixture the Fixture to invoke the test method on. + */ + TestCaller(std::string name, TestMethod test, Fixture& fixture) : + TestCase( name ), + m_ownFixture( false ), + m_fixture( &fixture ), + m_test( test ) + { + } + + /*! + * Constructor for TestCaller. + * This constructor does not create a new Fixture instance but accepts + * an existing one as parameter. The TestCaller will own the + * Fixture object and delete it in its destructor. + * \param name name of this TestCaller + * \param test the method this TestCaller calls in runTest() + * \param fixture the Fixture to invoke the test method on. + */ + TestCaller(std::string name, TestMethod test, Fixture* fixture) : + TestCase( name ), + m_ownFixture( true ), + m_fixture( fixture ), + m_test( test ) + { + } + + ~TestCaller() + { + if (m_ownFixture) + delete m_fixture; + } + + void runTest() + { +// try { + (m_fixture->*m_test)(); +// } +// catch ( ExpectedException & ) { +// return; +// } + +// ExpectedExceptionTraits::expectedException(); + } + + void setUp() + { + m_fixture->setUp (); + } + + void tearDown() + { + m_fixture->tearDown (); + } + + std::string toString() const + { + return "TestCaller " + getName(); + } + +private: + TestCaller( const TestCaller &other ); + TestCaller &operator =( const TestCaller &other ); + +private: + bool m_ownFixture; + Fixture *m_fixture; + TestMethod m_test; +}; + + + +CPPUNIT_NS_END + +#endif // CPPUNIT_TESTCALLER_H diff --git a/3rdParty/include/cppunit/TestCase.h b/3rdParty/include/cppunit/TestCase.h new file mode 100644 index 0000000000..d4b7a46a8f --- /dev/null +++ b/3rdParty/include/cppunit/TestCase.h @@ -0,0 +1,55 @@ +#ifndef CPPUNIT_TESTCASE_H +#define CPPUNIT_TESTCASE_H + +#include +#include +#include +#include +#include + + +CPPUNIT_NS_BEGIN + + +class TestResult; + + +/*! \brief A single test object. + * + * This class is used to implement a simple test case: define a subclass + * that overrides the runTest method. + * + * You don't usually need to use that class, but TestFixture and TestCaller instead. + * + * You are expected to subclass TestCase is you need to write a class similiar + * to TestCaller. + */ +class CPPUNIT_API TestCase : public TestLeaf, + public TestFixture +{ +public: + + TestCase( const std::string &name ); + + TestCase(); + + ~TestCase(); + + virtual void run(TestResult *result); + + std::string getName() const; + + //! FIXME: this should probably be pure virtual. + virtual void runTest(); + +private: + TestCase( const TestCase &other ); + TestCase &operator=( const TestCase &other ); + +private: + const std::string m_name; +}; + +CPPUNIT_NS_END + +#endif // CPPUNIT_TESTCASE_H diff --git a/3rdParty/include/cppunit/TestComposite.h b/3rdParty/include/cppunit/TestComposite.h new file mode 100644 index 0000000000..0ded95fcdb --- /dev/null +++ b/3rdParty/include/cppunit/TestComposite.h @@ -0,0 +1,45 @@ +#ifndef CPPUNIT_TESTCOMPSITE_H // -*- C++ -*- +#define CPPUNIT_TESTCOMPSITE_H + +#include +#include + +CPPUNIT_NS_BEGIN + + +/*! \brief A Composite of Tests. + * + * Base class for all test composites. Subclass this class if you need to implement + * a custom TestSuite. + * + * \see Test, TestSuite. + */ +class CPPUNIT_API TestComposite : public Test +{ +public: + TestComposite( const std::string &name = "" ); + + ~TestComposite(); + + void run( TestResult *result ); + + int countTestCases() const; + + std::string getName() const; + +private: + TestComposite( const TestComposite &other ); + TestComposite &operator =( const TestComposite &other ); + + virtual void doStartSuite( TestResult *controller ); + virtual void doRunChildTests( TestResult *controller ); + virtual void doEndSuite( TestResult *controller ); + +private: + const std::string m_name; +}; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_TESTCOMPSITE_H diff --git a/3rdParty/include/cppunit/TestFailure.h b/3rdParty/include/cppunit/TestFailure.h new file mode 100644 index 0000000000..64199790df --- /dev/null +++ b/3rdParty/include/cppunit/TestFailure.h @@ -0,0 +1,58 @@ +#ifndef CPPUNIT_TESTFAILURE_H // -*- C++ -*- +#define CPPUNIT_TESTFAILURE_H + +#include +#include + +CPPUNIT_NS_BEGIN + + +class Exception; +class SourceLine; +class Test; + + +/*! \brief Record of a failed Test execution. + * \ingroup BrowsingCollectedTestResult + * + * A TestFailure collects a failed test together with + * the caught exception. + * + * TestFailure assumes lifetime control for any exception + * passed to it. + */ +class CPPUNIT_API TestFailure +{ +public: + TestFailure( Test *failedTest, + Exception *thrownException, + bool isError ); + + virtual ~TestFailure (); + + virtual Test *failedTest() const; + + virtual Exception *thrownException() const; + + virtual SourceLine sourceLine() const; + + virtual bool isError() const; + + virtual std::string failedTestName() const; + + virtual TestFailure *clone() const; + +protected: + Test *m_failedTest; + Exception *m_thrownException; + bool m_isError; + +private: + TestFailure( const TestFailure &other ); + TestFailure &operator =( const TestFailure& other ); +}; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_TESTFAILURE_H diff --git a/3rdParty/include/cppunit/TestFixture.h b/3rdParty/include/cppunit/TestFixture.h new file mode 100644 index 0000000000..1223adbc93 --- /dev/null +++ b/3rdParty/include/cppunit/TestFixture.h @@ -0,0 +1,99 @@ +#ifndef CPPUNIT_TESTFIXTURE_H // -*- C++ -*- +#define CPPUNIT_TESTFIXTURE_H + +#include + +CPPUNIT_NS_BEGIN + + +/*! \brief Wraps a test case with setUp and tearDown methods. + * \ingroup WritingTestFixture + * + * A TestFixture is used to provide a common environment for a set + * of test cases. + * + * To define a test fixture, do the following: + * - implement a subclass of TestCase + * - the fixture is defined by instance variables + * - initialize the fixture state by overriding setUp + * (i.e. construct the instance variables of the fixture) + * - clean-up after a test by overriding tearDown. + * + * Each test runs in its own fixture so there + * can be no side effects among test runs. + * Here is an example: + * + * \code + * class MathTest : public CppUnit::TestFixture { + * protected: + * int m_value1, m_value2; + * + * public: + * MathTest() {} + * + * void setUp () { + * m_value1 = 2; + * m_value2 = 3; + * } + * } + * \endcode + * + * For each test implement a method which interacts + * with the fixture. Verify the expected results with assertions specified + * by calling CPPUNIT_ASSERT on the expression you want to test: + * + * \code + * public: + * void testAdd () { + * int result = m_value1 + m_value2; + * CPPUNIT_ASSERT( result == 5 ); + * } + * \endcode + * + * Once the methods are defined you can run them. To do this, use + * a TestCaller. + * + * \code + * CppUnit::Test *test = new CppUnit::TestCaller( "testAdd", + * &MathTest::testAdd ); + * test->run(); + * \endcode + * + * + * The tests to be run can be collected into a TestSuite. + * + * \code + * public: + * static CppUnit::TestSuite *MathTest::suite () { + * CppUnit::TestSuite *suiteOfTests = new CppUnit::TestSuite; + * suiteOfTests->addTest(new CppUnit::TestCaller( + * "testAdd", &MathTest::testAdd)); + * suiteOfTests->addTest(new CppUnit::TestCaller( + * "testDivideByZero", &MathTest::testDivideByZero)); + * return suiteOfTests; + * } + * \endcode + * + * A set of macros have been created for convenience. They are located in HelperMacros.h. + * + * \see TestResult, TestSuite, TestCaller, + * \see CPPUNIT_TEST_SUB_SUITE, CPPUNIT_TEST, CPPUNIT_TEST_SUITE_END, + * \see CPPUNIT_TEST_SUITE_REGISTRATION, CPPUNIT_TEST_EXCEPTION, CPPUNIT_TEST_FAIL. + */ +class CPPUNIT_API TestFixture +{ +public: + virtual ~TestFixture() {}; + + //! \brief Set up context before running a test. + virtual void setUp() {}; + + //! Clean up after the test run. + virtual void tearDown() {}; +}; + + +CPPUNIT_NS_END + + +#endif diff --git a/3rdParty/include/cppunit/TestLeaf.h b/3rdParty/include/cppunit/TestLeaf.h new file mode 100644 index 0000000000..c83b075975 --- /dev/null +++ b/3rdParty/include/cppunit/TestLeaf.h @@ -0,0 +1,44 @@ +#ifndef CPPUNIT_TESTLEAF_H +#define CPPUNIT_TESTLEAF_H + +#include + + +CPPUNIT_NS_BEGIN + + +/*! \brief A single test object. + * + * Base class for single test case: a test that doesn't have any children. + * + */ +class CPPUNIT_API TestLeaf: public Test +{ +public: + /*! Returns 1 as the default number of test cases invoked by run(). + * + * You may override this method when many test cases are invoked (RepeatedTest + * for example). + * + * \return 1. + * \see Test::countTestCases(). + */ + int countTestCases() const; + + /*! Returns the number of child of this test case: 0. + * + * You should never override this method: a TestLeaf as no children by definition. + * + * \return 0. + */ + int getChildTestCount() const; + + /*! Always throws std::out_of_range. + * \see Test::doGetChildTestAt(). + */ + Test *doGetChildTestAt( int index ) const; +}; + +CPPUNIT_NS_END + +#endif // CPPUNIT_TESTLEAF_H diff --git a/3rdParty/include/cppunit/TestListener.h b/3rdParty/include/cppunit/TestListener.h new file mode 100644 index 0000000000..330262d331 --- /dev/null +++ b/3rdParty/include/cppunit/TestListener.h @@ -0,0 +1,148 @@ +#ifndef CPPUNIT_TESTLISTENER_H // -*- C++ -*- +#define CPPUNIT_TESTLISTENER_H + +#include + + +CPPUNIT_NS_BEGIN + + +class Exception; +class Test; +class TestFailure; +class TestResult; + + +/*! \brief Listener for test progress and result. + * \ingroup TrackingTestExecution + * + * Implementing the Observer pattern a TestListener may be registered + * to a TestResult to obtain information on the testing progress. Use + * specialized sub classes of TestListener for text output + * (TextTestProgressListener). Do not use the Listener for the test + * result output, use a subclass of Outputter instead. + * + * The test framework distinguishes between failures and errors. + * A failure is anticipated and checked for with assertions. Errors are + * unanticipated problems signified by exceptions that are not generated + * by the framework. + * + * Here is an example to track test time: + * + * + * \code + * #include + * #include + * #include // for clock() + * + * class TimingListener : public CppUnit::TestListener + * { + * public: + * void startTest( CppUnit::Test *test ) + * { + * _chronometer.start(); + * } + * + * void endTest( CppUnit::Test *test ) + * { + * _chronometer.end(); + * addTest( test, _chronometer.elapsedTime() ); + * } + * + * // ... (interface to add/read test timing result) + * + * private: + * Clock _chronometer; + * }; + * \endcode + * + * And another example that track failure/success at test suite level and captures + * the TestPath of each suite: + * \code + * class SuiteTracker : public CppUnit::TestListener + * { + * public: + * void startSuite( CppUnit::Test *suite ) + * { + * m_currentPath.add( suite ); + * } + * + * void addFailure( const TestFailure &failure ) + * { + * m_suiteFailure.top() = false; + * } + * + * void endSuite( CppUnit::Test *suite ) + * { + * m_suiteStatus.insert( std::make_pair( suite, m_suiteFailure.top() ) ); + * m_suitePaths.insert( std::make_pair( suite, m_currentPath ) ); + * + * m_currentPath.up(); + * m_suiteFailure.pop(); + * } + * + * private: + * std::stack m_suiteFailure; + * CppUnit::TestPath m_currentPath; + * std::map m_suiteStatus; + * std::map m_suitePaths; + * }; + * \endcode + * + * \see TestResult + */ +class CPPUNIT_API TestListener +{ +public: + virtual ~TestListener() {} + + /// Called when just before a TestCase is run. + virtual void startTest( Test * /*test*/ ) {} + + /*! \brief Called when a failure occurs while running a test. + * \see TestFailure. + * \warning \a failure is a temporary object that is destroyed after the + * method call. Use TestFailure::clone() to create a duplicate. + */ + virtual void addFailure( const TestFailure & /*failure*/ ) {} + + /// Called just after a TestCase was run (even if a failure occured). + virtual void endTest( Test * /*test*/ ) {} + + /*! \brief Called by a TestComposite just before running its child tests. + */ + virtual void startSuite( Test * /*suite*/ ) {} + + /*! \brief Called by a TestComposite after running its child tests. + */ + virtual void endSuite( Test * /*suite*/ ) {} + + /*! \brief Called by a TestRunner before running the test. + * + * You can use this to do some global initialisation. A listener + * could also use to output a 'prolog' to the test run. + * + * \param test Test that is going to be run. + * \param eventManager Event manager used for the test run. + */ + virtual void startTestRun( Test * /*test*/, + TestResult * /*eventManager*/ ) {} + + /*! \brief Called by a TestRunner after running the test. + * + * TextTestProgressListener use this to emit a line break. You can also use this + * to do some global uninitialisation. + * + * \param test Test that was run. + * \param eventManager Event manager used for the test run. + */ + virtual void endTestRun( Test * /*test*/, + TestResult * /*eventManager*/ ) {} +}; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_TESTLISTENER_H + + diff --git a/3rdParty/include/cppunit/TestPath.h b/3rdParty/include/cppunit/TestPath.h new file mode 100644 index 0000000000..c3c851cc5b --- /dev/null +++ b/3rdParty/include/cppunit/TestPath.h @@ -0,0 +1,211 @@ +#ifndef CPPUNIT_TESTPATH_H +#define CPPUNIT_TESTPATH_H + +#include + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( push ) +#pragma warning( disable: 4251 ) // X needs to have dll-interface to be used by clients of class Z +#endif + +#include +#include + +CPPUNIT_NS_BEGIN + + +class Test; + +#if CPPUNIT_NEED_DLL_DECL +// template class CPPUNIT_API std::deque; +#endif + + +/*! \brief A List of Test representing a path to access a Test. + * \ingroup ExecutingTest + * + * The path can be converted to a string and resolved from a string with toString() + * and TestPath( Test *root, const std::string &pathAsString ). + * + * Pointed tests are not owned by the class. + * + * \see Test::resolvedTestPath() + */ +class CPPUNIT_API TestPath +{ +public: + /*! \brief Constructs an invalid path. + * + * The path is invalid until a test is added with add(). + */ + TestPath(); + + /*! \brief Constructs a valid path. + * + * \param root Test to add. + */ + TestPath( Test *root ); + + /*! \brief Constructs a path using a slice of another path. + * \param otherPath Path the test are copied from. + * \param indexFirst Zero based index of the first test to copy. Adjusted to be in valid + * range. \a count is adjusted with \a indexFirst. + * \param count Number of tests to copy. If < 0 then all test starting from index + * \a indexFirst are copied. + */ + TestPath( const TestPath &otherPath, + int indexFirst, + int count = -1 ); + + /*! \brief Resolves a path from a string returned by toString(). + * + * If \a pathAsString is an absolute path (begins with '/'), then the first test name + * of the path must be the name of \a searchRoot. Otherwise, \a pathAsString is a + * relative path, and the first test found using Test::findTest() matching the first + * test name is used as root. An empty string resolve to a path containing + * \a searchRoot. + * + * The resolved path is always valid. + * + * \param searchRoot Test used to resolve the path. + * \param pathAsString String that contains the path as a string created by toString(). + * \exception std::invalid_argument if one of the test names can not be resolved. + * \see toString(). + */ + TestPath( Test *searchRoot, + const std::string &pathAsString ); + + /*! \brief Copy constructor. + * \param other Object to copy. + */ + TestPath( const TestPath &other ); + + virtual ~TestPath(); + + /*! \brief Tests if the path contains at least one test. + * \return \c true if the path contains at least one test, otherwise returns \c false. + */ + virtual bool isValid() const; + + /*! \brief Adds a test to the path. + * \param test Pointer on the test to add. Must not be \c NULL. + */ + virtual void add( Test *test ); + + /*! \brief Adds all the tests of the specified path. + * \param path Path that contains the test to add. + */ + virtual void add( const TestPath &path ); + + /*! \brief Inserts a test at the specified index. + * \param test Pointer on the test to insert. Must not be \c NULL. + * \param index Zero based index indicating where the test is inserted. + * \exception std::out_of_range is \a index < 0 or \a index > getTestCount(). + */ + virtual void insert( Test *test, int index ); + + /*! \brief Inserts all the tests at the specified path at a given index. + * \param path Path that contains the test to insert. + * \param index Zero based index indicating where the tests are inserted. + * \exception std::out_of_range is \a index < 0 or \a index > getTestCount(), and + * \a path is valid. + */ + virtual void insert( const TestPath &path, int index ); + + /*! \brief Removes all the test from the path. + * + * The path becomes invalid after this call. + */ + virtual void removeTests(); + + /*! \brief Removes the test at the specified index of the path. + * \param index Zero based index of the test to remove. + * \exception std::out_of_range is \a index < 0 or \a index >= getTestCount(). + */ + virtual void removeTest( int index ); + + /*! \brief Removes the last test. + * \exception std::out_of_range is the path is invalid. + * \see isValid(). + */ + virtual void up(); + + /*! \brief Returns the number of tests in the path. + * \return Number of tests in the path. + */ + virtual int getTestCount() const; + + /*! \brief Returns the test of the specified index. + * \param index Zero based index of the test to return. + * \return Pointer on the test at index \a index. Never \c NULL. + * \exception std::out_of_range is \a index < 0 or \a index >= getTestCount(). + */ + virtual Test *getTestAt( int index ) const; + + /*! \brief Get the last test of the path. + * \return Pointer on the last test (test at the bottom of the hierarchy). Never \c NULL. + * \exception std::out_of_range if the path is not valid ( isValid() returns \c false ). + */ + virtual Test *getChildTest() const; + + /*! \brief Returns the path as a string. + * + * For example, if a path is composed of three tests named "All Tests", "Math" and + * "Math::testAdd", toString() will return: + * + * "All Tests/Math/Math::testAdd". + * + * \return A string composed of the test names separated with a '/'. It is a relative + * path. + */ + virtual std::string toString() const; + + /*! \brief Assignment operator. + * \param other Object to copy. + * \return This object. + */ + TestPath &operator =( const TestPath &other ); + +protected: + /*! \brief Checks that the specified test index is within valid range. + * \param index Zero based index to check. + * \exception std::out_of_range is \a index < 0 or \a index >= getTestCount(). + */ + void checkIndexValid( int index ) const; + + /// A list of test names. + typedef CppUnitDeque PathTestNames; + + /*! \brief Splits a path string into its test name components. + * \param pathAsString Path string created with toString(). + * \param testNames Test name components are added to that container. + * \return \c true if the path is relative (does not begin with '/'), \c false + * if it is absolute (begin with '/'). + */ + bool splitPathString( const std::string &pathAsString, + PathTestNames &testNames ); + + /*! \brief Finds the actual root of a path string and get the path string name components. + * \param searchRoot Test used as root if the path string is absolute, or to search + * the root test if the path string is relative. + * \param pathAsString Path string. May be absolute or relative. + * \param testNames Test name components are added to that container. + * \return Pointer on the resolved root test. Never \c NULL. + * \exception std::invalid_argument if either the root name can not be resolved or if + * pathAsString contains no name components. + */ + Test *findActualRoot( Test *searchRoot, + const std::string &pathAsString, + PathTestNames &testNames ); + +protected: + typedef CppUnitDeque Tests; + Tests m_tests; + +}; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_TESTPATH_H + diff --git a/3rdParty/include/cppunit/TestResult.h b/3rdParty/include/cppunit/TestResult.h new file mode 100644 index 0000000000..e7e1050a08 --- /dev/null +++ b/3rdParty/include/cppunit/TestResult.h @@ -0,0 +1,156 @@ +#ifndef CPPUNIT_TESTRESULT_H +#define CPPUNIT_TESTRESULT_H + +#include + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( push ) +#pragma warning( disable: 4251 ) // X needs to have dll-interface to be used by clients of class Z +#endif + +#include +#include +#include + +CPPUNIT_NS_BEGIN + + +class Exception; +class Functor; +class Protector; +class ProtectorChain; +class Test; +class TestFailure; +class TestListener; + + +#if CPPUNIT_NEED_DLL_DECL +// template class CPPUNIT_API std::deque; +#endif + +/*! \brief Manages TestListener. + * \ingroup TrackingTestExecution + * + * A single instance of this class is used when running the test. It is usually + * created by the test runner (TestRunner). + * + * This class shouldn't have to be inherited from. Use a TestListener + * or one of its subclasses to be informed of the ongoing tests. + * Use a Outputter to receive a test summary once it has finished + * + * TestResult supplies a template method 'setSynchronizationObject()' + * so that subclasses can provide mutual exclusion in the face of multiple + * threads. This can be useful when tests execute in one thread and + * they fill a subclass of TestResult which effects change in another + * thread. To have mutual exclusion, override setSynchronizationObject() + * and make sure that you create an instance of ExclusiveZone at the + * beginning of each method. + * + * \see Test, TestListener, TestResultCollector, Outputter. + */ +class CPPUNIT_API TestResult : protected SynchronizedObject +{ +public: + /// Construct a TestResult + TestResult( SynchronizationObject *syncObject = 0 ); + + /// Destroys a test result + virtual ~TestResult(); + + virtual void addListener( TestListener *listener ); + + virtual void removeListener( TestListener *listener ); + + /// Resets the stop flag. + virtual void reset(); + + /// Stop testing + virtual void stop(); + + /// Returns whether testing should be stopped + virtual bool shouldStop() const; + + /// Informs TestListener that a test will be started. + virtual void startTest( Test *test ); + + /*! \brief Adds an error to the list of errors. + * The passed in exception + * caused the error + */ + virtual void addError( Test *test, Exception *e ); + + /*! \brief Adds a failure to the list of failures. The passed in exception + * caused the failure. + */ + virtual void addFailure( Test *test, Exception *e ); + + /// Informs TestListener that a test was completed. + virtual void endTest( Test *test ); + + /// Informs TestListener that a test suite will be started. + virtual void startSuite( Test *test ); + + /// Informs TestListener that a test suite was completed. + virtual void endSuite( Test *test ); + + /*! \brief Run the specified test. + * + * Calls startTestRun(), test->run(this), and finally endTestRun(). + */ + virtual void runTest( Test *test ); + + /*! \brief Protects a call to the specified functor. + * + * See Protector to understand how protector works. A default protector is + * always present. It captures CppUnit::Exception, std::exception and + * any other exceptions, retrieving as much as possible information about + * the exception as possible. + * + * Additional Protector can be added to the chain to support other exception + * types using pushProtector() and popProtector(). + * + * \param functor Functor to call (typically a call to setUp(), runTest() or + * tearDown(). + * \param test Test the functor is associated to (used for failure reporting). + * \param shortDescription Short description override for the failure message. + */ + virtual bool protect( const Functor &functor, + Test *test, + const std::string &shortDescription = std::string("") ); + + /// Adds the specified protector to the protector chain. + virtual void pushProtector( Protector *protector ); + + /// Removes the last protector from the protector chain. + virtual void popProtector(); + +protected: + /*! \brief Called to add a failure to the list of failures. + */ + void addFailure( const TestFailure &failure ); + + virtual void startTestRun( Test *test ); + virtual void endTestRun( Test *test ); + +protected: + typedef CppUnitDeque TestListeners; + TestListeners m_listeners; + ProtectorChain *m_protectorChain; + bool m_stop; + +private: + TestResult( const TestResult &other ); + TestResult &operator =( const TestResult &other ); +}; + + +CPPUNIT_NS_END + + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( pop ) +#endif + +#endif // CPPUNIT_TESTRESULT_H + + diff --git a/3rdParty/include/cppunit/TestResultCollector.h b/3rdParty/include/cppunit/TestResultCollector.h new file mode 100644 index 0000000000..01b0a547ab --- /dev/null +++ b/3rdParty/include/cppunit/TestResultCollector.h @@ -0,0 +1,87 @@ +#ifndef CPPUNIT_TESTRESULTCOLLECTOR_H +#define CPPUNIT_TESTRESULTCOLLECTOR_H + +#include + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( push ) +#pragma warning( disable: 4251 4660 ) // X needs to have dll-interface to be used by clients of class Z +#endif + +#include +#include + + +CPPUNIT_NS_BEGIN + +#if CPPUNIT_NEED_DLL_DECL +// template class CPPUNIT_API std::deque; +// template class CPPUNIT_API std::deque; +#endif + + +/*! \brief Collects test result. + * \ingroup WritingTestResult + * \ingroup BrowsingCollectedTestResult + * + * A TestResultCollector is a TestListener which collects the results of executing + * a test case. It is an instance of the Collecting Parameter pattern. + * + * The test framework distinguishes between failures and errors. + * A failure is anticipated and checked for with assertions. Errors are + * unanticipated problems signified by exceptions that are not generated + * by the framework. + * \see TestListener, TestFailure. + */ +class CPPUNIT_API TestResultCollector : public TestSuccessListener +{ +public: + typedef CppUnitDeque TestFailures; + typedef CppUnitDeque Tests; + + + /*! Constructs a TestResultCollector object. + */ + TestResultCollector( SynchronizationObject *syncObject = 0 ); + + /// Destructor. + virtual ~TestResultCollector(); + + void startTest( Test *test ); + void addFailure( const TestFailure &failure ); + + virtual void reset(); + + virtual int runTests() const; + virtual int testErrors() const; + virtual int testFailures() const; + virtual int testFailuresTotal() const; + + virtual const TestFailures& failures() const; + virtual const Tests &tests() const; + +protected: + void freeFailures(); + + Tests m_tests; + TestFailures m_failures; + int m_testErrors; + +private: + /// Prevents the use of the copy constructor. + TestResultCollector( const TestResultCollector © ); + + /// Prevents the use of the copy operator. + void operator =( const TestResultCollector © ); +}; + + + +CPPUNIT_NS_END + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( pop ) +#endif + + +#endif // CPPUNIT_TESTRESULTCOLLECTOR_H diff --git a/3rdParty/include/cppunit/TestRunner.h b/3rdParty/include/cppunit/TestRunner.h new file mode 100644 index 0000000000..930370ad97 --- /dev/null +++ b/3rdParty/include/cppunit/TestRunner.h @@ -0,0 +1,135 @@ +#ifndef CPPUNIT_TESTRUNNER_H +#define CPPUNIT_TESTRUNNER_H + +#include +#include + +CPPUNIT_NS_BEGIN + + +class Test; +class TestResult; + + +/*! \brief Generic test runner. + * \ingroup ExecutingTest + * + * The TestRunner assumes ownership of all added tests: you can not add test + * or suite that are local variable since they can't be deleted. + * + * Example of usage: + * \code + * #include + * #include + * #include + * #include + * #include + * #include + * + * + * int + * main( int argc, char* argv[] ) + * { + * std::string testPath = (argc > 1) ? std::string(argv[1]) : ""; + * + * // Create the event manager and test controller + * CppUnit::TestResult controller; + * + * // Add a listener that colllects test result + * CppUnit::TestResultCollector result; + * controller.addListener( &result ); + * + * // Add a listener that print dots as test run. + * CppUnit::TextTestProgressListener progress; + * controller.addListener( &progress ); + * + * // Add the top suite to the test runner + * CppUnit::TestRunner runner; + * runner.addTest( CppUnit::TestFactoryRegistry::getRegistry().makeTest() ); + * try + * { + * std::cout << "Running " << testPath; + * runner.run( controller, testPath ); + * + * std::cerr << std::endl; + * + * // Print test in a compiler compatible format. + * CppUnit::CompilerOutputter outputter( &result, std::cerr ); + * outputter.write(); + * } + * catch ( std::invalid_argument &e ) // Test path not resolved + * { + * std::cerr << std::endl + * << "ERROR: " << e.what() + * << std::endl; + * return 0; + * } + * + * return result.wasSuccessful() ? 0 : 1; + * } + * \endcode + */ +class CPPUNIT_API TestRunner +{ +public: + /*! \brief Constructs a TestRunner object. + */ + TestRunner( ); + + /// Destructor. + virtual ~TestRunner(); + + /*! \brief Adds the specified test. + * \param test Test to add. The TestRunner takes ownership of the test. + */ + virtual void addTest( Test *test ); + + /*! \brief Runs a test using the specified controller. + * \param controller Event manager and controller used for testing + * \param testPath Test path string. See Test::resolveTestPath() for detail. + * \exception std::invalid_argument if no test matching \a testPath is found. + * see TestPath::TestPath( Test*, const std::string &) + * for detail. + */ + virtual void run( TestResult &controller, + const std::string &testPath = "" ); + +protected: + /*! \brief (INTERNAL) Mutating test suite. + */ + class CPPUNIT_API WrappingSuite : public TestSuite + { + public: + WrappingSuite( const std::string &name = "All Tests" ); + + int getChildTestCount() const; + + std::string getName() const; + + void run( TestResult *result ); + + protected: + Test *doGetChildTestAt( int index ) const; + + bool hasOnlyOneTest() const; + + Test *getUniqueChildTest() const; + }; + +protected: + WrappingSuite *m_suite; + +private: + /// Prevents the use of the copy constructor. + TestRunner( const TestRunner © ); + + /// Prevents the use of the copy operator. + void operator =( const TestRunner © ); + +private: +}; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_TESTRUNNER_H diff --git a/3rdParty/include/cppunit/TestSuccessListener.h b/3rdParty/include/cppunit/TestSuccessListener.h new file mode 100644 index 0000000000..60c5ff5004 --- /dev/null +++ b/3rdParty/include/cppunit/TestSuccessListener.h @@ -0,0 +1,39 @@ +#ifndef CPPUNIT_TESTSUCCESSLISTENER_H +#define CPPUNIT_TESTSUCCESSLISTENER_H + +#include +#include + + +CPPUNIT_NS_BEGIN + + +/*! \brief TestListener that checks if any test case failed. + * \ingroup TrackingTestExecution + */ +class CPPUNIT_API TestSuccessListener : public TestListener, + public SynchronizedObject +{ +public: + /*! Constructs a TestSuccessListener object. + */ + TestSuccessListener( SynchronizationObject *syncObject = 0 ); + + /// Destructor. + virtual ~TestSuccessListener(); + + virtual void reset(); + + void addFailure( const TestFailure &failure ); + + /// Returns whether the entire test was successful or not. + virtual bool wasSuccessful() const; + +private: + bool m_success; +}; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_TESTSUCCESSLISTENER_H diff --git a/3rdParty/include/cppunit/TestSuite.h b/3rdParty/include/cppunit/TestSuite.h new file mode 100644 index 0000000000..2b9cd8d497 --- /dev/null +++ b/3rdParty/include/cppunit/TestSuite.h @@ -0,0 +1,80 @@ +#ifndef CPPUNIT_TESTSUITE_H // -*- C++ -*- +#define CPPUNIT_TESTSUITE_H + +#include + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( push ) +#pragma warning( disable: 4251 ) // X needs to have dll-interface to be used by clients of class Z +#endif + +#include +#include + +CPPUNIT_NS_BEGIN + + +#if CPPUNIT_NEED_DLL_DECL +// template class CPPUNIT_API std::vector; +#endif + + +/*! \brief A Composite of Tests. + * \ingroup CreatingTestSuite + * + * It runs a collection of test cases. Here is an example. + * \code + * CppUnit::TestSuite *suite= new CppUnit::TestSuite(); + * suite->addTest(new CppUnit::TestCaller ( + * "testAdd", testAdd)); + * suite->addTest(new CppUnit::TestCaller ( + * "testDivideByZero", testDivideByZero)); + * \endcode + * Note that \link TestSuite TestSuites \endlink assume lifetime + * control for any tests added to them. + * + * TestSuites do not register themselves in the TestRegistry. + * \see Test + * \see TestCaller + */ +class CPPUNIT_API TestSuite : public TestComposite +{ +public: + /*! Constructs a test suite with the specified name. + */ + TestSuite( std::string name = "" ); + + ~TestSuite(); + + /*! Adds the specified test to the suite. + * \param test Test to add. Must not be \c NULL. + */ + void addTest( Test *test ); + + /*! Returns the list of the tests (DEPRECATED). + * \deprecated Use getChildTestCount() & getChildTestAt() of the + * TestComposite interface instead. + * \return Reference on a vector that contains the tests of the suite. + */ + const CppUnitVector &getTests() const; + + /*! Destroys all the tests of the suite. + */ + virtual void deleteContents(); + + int getChildTestCount() const; + + Test *doGetChildTestAt( int index ) const; + +private: + CppUnitVector m_tests; +}; + + +CPPUNIT_NS_END + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( pop ) +#endif + +#endif // CPPUNIT_TESTSUITE_H diff --git a/3rdParty/include/cppunit/TextOutputter.h b/3rdParty/include/cppunit/TextOutputter.h new file mode 100644 index 0000000000..6bd9ceaae1 --- /dev/null +++ b/3rdParty/include/cppunit/TextOutputter.h @@ -0,0 +1,59 @@ +#ifndef CPPUNIT_TEXTOUTPUTTER_H +#define CPPUNIT_TEXTOUTPUTTER_H + +#include +#include +#include + +CPPUNIT_NS_BEGIN + + +class Exception; +class SourceLine; +class TestResultCollector; +class TestFailure; + + +/*! \brief Prints a TestResultCollector to a text stream. + * \ingroup WritingTestResult + */ +class CPPUNIT_API TextOutputter : public Outputter +{ +public: + TextOutputter( TestResultCollector *result, + OStream &stream ); + + /// Destructor. + virtual ~TextOutputter(); + + void write(); + virtual void printFailures(); + virtual void printHeader(); + + virtual void printFailure( TestFailure *failure, + int failureNumber ); + virtual void printFailureListMark( int failureNumber ); + virtual void printFailureTestName( TestFailure *failure ); + virtual void printFailureType( TestFailure *failure ); + virtual void printFailureLocation( SourceLine sourceLine ); + virtual void printFailureDetail( Exception *thrownException ); + virtual void printFailureWarning(); + virtual void printStatistics(); + +protected: + TestResultCollector *m_result; + OStream &m_stream; + +private: + /// Prevents the use of the copy constructor. + TextOutputter( const TextOutputter © ); + + /// Prevents the use of the copy operator. + void operator =( const TextOutputter © ); +}; + + + +CPPUNIT_NS_END + +#endif // CPPUNIT_TEXTOUTPUTTER_H diff --git a/3rdParty/include/cppunit/TextTestProgressListener.h b/3rdParty/include/cppunit/TextTestProgressListener.h new file mode 100644 index 0000000000..7521c40bcd --- /dev/null +++ b/3rdParty/include/cppunit/TextTestProgressListener.h @@ -0,0 +1,44 @@ +#ifndef CPPUNIT_TEXTTESTPROGRESSLISTENER_H +#define CPPUNIT_TEXTTESTPROGRESSLISTENER_H + +#include + + +CPPUNIT_NS_BEGIN + + +/*! + * \brief TestListener that show the status of each TestCase test result. + * \ingroup TrackingTestExecution + */ +class CPPUNIT_API TextTestProgressListener : public TestListener +{ +public: + /*! Constructs a TextTestProgressListener object. + */ + TextTestProgressListener(); + + /// Destructor. + virtual ~TextTestProgressListener(); + + void startTest( Test *test ); + + void addFailure( const TestFailure &failure ); + + void endTestRun( Test *test, + TestResult *eventManager ); + +private: + /// Prevents the use of the copy constructor. + TextTestProgressListener( const TextTestProgressListener © ); + + /// Prevents the use of the copy operator. + void operator =( const TextTestProgressListener © ); + +private: +}; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_TEXTTESTPROGRESSLISTENER_H diff --git a/3rdParty/include/cppunit/TextTestResult.h b/3rdParty/include/cppunit/TextTestResult.h new file mode 100644 index 0000000000..e7b1fa3ea6 --- /dev/null +++ b/3rdParty/include/cppunit/TextTestResult.h @@ -0,0 +1,39 @@ +#ifndef CPPUNIT_TEXTTESTRESULT_H +#define CPPUNIT_TEXTTESTRESULT_H + +#include +#include +#include + +CPPUNIT_NS_BEGIN + + +class SourceLine; +class Exception; +class Test; + +/*! \brief Holds printable test result (DEPRECATED). + * \ingroup TrackingTestExecution + * + * deprecated Use class TextTestProgressListener and TextOutputter instead. + */ +class CPPUNIT_API TextTestResult : public TestResult, + public TestResultCollector +{ +public: + TextTestResult(); + + virtual void addFailure( const TestFailure &failure ); + virtual void startTest( Test *test ); + virtual void print( OStream &stream ); +}; + +/** insertion operator for easy output */ +CPPUNIT_API OStream &operator <<( OStream &stream, + TextTestResult &result ); + +CPPUNIT_NS_END + +#endif // CPPUNIT_TEXTTESTRESULT_H + + diff --git a/3rdParty/include/cppunit/TextTestRunner.h b/3rdParty/include/cppunit/TextTestRunner.h new file mode 100644 index 0000000000..23890e0865 --- /dev/null +++ b/3rdParty/include/cppunit/TextTestRunner.h @@ -0,0 +1,6 @@ +#ifndef CPPUNIT_TEXTTESTRUNNER_H +#define CPPUNIT_TEXTTESTRUNNER_H + +#include + +#endif // CPPUNIT_TEXTTESTRUNNER_H diff --git a/3rdParty/include/cppunit/XmlOutputter.h b/3rdParty/include/cppunit/XmlOutputter.h new file mode 100644 index 0000000000..0de9676d8f --- /dev/null +++ b/3rdParty/include/cppunit/XmlOutputter.h @@ -0,0 +1,167 @@ +#ifndef CPPUNIT_XMLTESTRESULTOUTPUTTER_H +#define CPPUNIT_XMLTESTRESULTOUTPUTTER_H + +#include + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( push ) +#pragma warning( disable: 4251 ) // X needs to have dll-interface to be used by clients of class Z +#endif + +#include +#include +#include +#include + + +CPPUNIT_NS_BEGIN + + +class Test; +class TestFailure; +class TestResultCollector; +class XmlDocument; +class XmlElement; +class XmlOutputterHook; + + +/*! \brief Outputs a TestResultCollector in XML format. + * \ingroup WritingTestResult + * + * Save the test result as a XML stream. + * + * Additional datas can be added to the XML document using XmlOutputterHook. + * Hook are not owned by the XmlOutputter. They should be valid until + * destruction of the XmlOutputter. They can be removed with removeHook(). + * + * \see XmlDocument, XmlElement, XmlOutputterHook. + */ +class CPPUNIT_API XmlOutputter : public Outputter +{ +public: + /*! \brief Constructs a XmlOutputter object. + * \param result Result of the test run. + * \param stream Stream used to output the XML output. + * \param encoding Encoding used in the XML file (default is Latin-1). + */ + XmlOutputter( TestResultCollector *result, + OStream &stream, + std::string encoding = std::string("ISO-8859-1") ); + + /// Destructor. + virtual ~XmlOutputter(); + + /*! \brief Adds the specified hook to the outputter. + * \param hook Hook to add. Must not be \c NULL. + */ + virtual void addHook( XmlOutputterHook *hook ); + + /*! \brief Removes the specified hook from the outputter. + * \param hook Hook to remove. + */ + virtual void removeHook( XmlOutputterHook *hook ); + + /*! \brief Writes the specified result as an XML document to the stream. + * + * Refer to examples/cppunittest/XmlOutputterTest.cpp for example + * of use and XML document structure. + */ + virtual void write(); + + /*! \brief Sets the XSL style sheet used. + * + * \param styleSheet Name of the style sheet used. If empty, then no style sheet + * is used (default). + */ + virtual void setStyleSheet( const std::string &styleSheet ); + + /*! \brief set the output document as standalone or not. + * + * For the output document, specify wether it's a standalone XML + * document, or not. + * + * \param standalone if true, the output will be specified as standalone. + * if false, it will be not. + */ + virtual void setStandalone( bool standalone ); + + typedef CppUnitMap > FailedTests; + + /*! \brief Sets the root element and adds its children. + * + * Set the root element of the XML Document and add its child elements. + * + * For all hooks, call beginDocument() just after creating the root element (it + * is empty at this time), and endDocument() once all the datas have been added + * to the root element. + */ + virtual void setRootNode(); + + virtual void addFailedTests( FailedTests &failedTests, + XmlElement *rootNode ); + + virtual void addSuccessfulTests( FailedTests &failedTests, + XmlElement *rootNode ); + + /*! \brief Adds the statics element to the root node. + * + * Creates a new element containing statistics data and adds it to the root element. + * Then, for all hooks, call statisticsAdded(). + * \param rootNode Root element. + */ + virtual void addStatistics( XmlElement *rootNode ); + + /*! \brief Adds a failed test to the failed tests node. + * Creates a new element containing datas about the failed test, and adds it to + * the failed tests element. + * Then, for all hooks, call failTestAdded(). + */ + virtual void addFailedTest( Test *test, + TestFailure *failure, + int testNumber, + XmlElement *testsNode ); + + virtual void addFailureLocation( TestFailure *failure, + XmlElement *testElement ); + + + /*! \brief Adds a successful test to the successful tests node. + * Creates a new element containing datas about the successful test, and adds it to + * the successful tests element. + * Then, for all hooks, call successfulTestAdded(). + */ + virtual void addSuccessfulTest( Test *test, + int testNumber, + XmlElement *testsNode ); +protected: + virtual void fillFailedTestsMap( FailedTests &failedTests ); + +protected: + typedef CppUnitDeque Hooks; + + TestResultCollector *m_result; + OStream &m_stream; + std::string m_encoding; + std::string m_styleSheet; + XmlDocument *m_xml; + Hooks m_hooks; + +private: + /// Prevents the use of the copy constructor. + XmlOutputter( const XmlOutputter © ); + + /// Prevents the use of the copy operator. + void operator =( const XmlOutputter © ); + +private: +}; + + +CPPUNIT_NS_END + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( pop ) +#endif + + +#endif // CPPUNIT_XMLTESTRESULTOUTPUTTER_H diff --git a/3rdParty/include/cppunit/XmlOutputterHook.h b/3rdParty/include/cppunit/XmlOutputterHook.h new file mode 100644 index 0000000000..5ded3b1ee8 --- /dev/null +++ b/3rdParty/include/cppunit/XmlOutputterHook.h @@ -0,0 +1,163 @@ +#ifndef CPPUNIT_XMLOUTPUTTERHOOK_H +#define CPPUNIT_XMLOUTPUTTERHOOK_H + +#include + + +CPPUNIT_NS_BEGIN + + +class Test; +class TestFailure; +class XmlDocument; +class XmlElement; + + + +/*! \brief Hook to customize Xml output. + * + * XmlOutputterHook can be passed to XmlOutputter to customize the XmlDocument. + * + * Common customizations are: + * - adding some datas to successfull or failed test with + * failTestAdded() and successfulTestAdded(), + * - adding some statistics with statisticsAdded(), + * - adding other datas with beginDocument() or endDocument(). + * + * See examples/ClockerPlugIn which makes use of most the hook. + * + * Another simple example of an outputter hook is shown below. It may be + * used to add some meta information to your result files. In the example, + * the author name as well as the project name and test creation date is + * added to the head of the xml file. + * + * In order to make this information stored within the xml file, the virtual + * member function beginDocument() is overriden where a new + * XmlElement object is created. + * + * This element is simply added to the root node of the document which + * makes the information automatically being stored when the xml file + * is written. + * + * \code + * #include + * #include + * #include + * + * ... + * + * class MyXmlOutputterHook : public CppUnit::XmlOutputterHook + * { + * public: + * MyXmlOutputterHook(const std::string projectName, + * const std::string author) + * { + * m_projectName = projectName; + * m_author = author; + * }; + * + * virtual ~MyXmlOutputterHook() + * { + * }; + * + * void beginDocument(CppUnit::XmlDocument* document) + * { + * if (!document) + * return; + * + * // dump current time + * std::string szDate = CppUnit::StringTools::toString( (int)time(0) ); + * CppUnit::XmlElement* metaEl = new CppUnit::XmlElement("SuiteInfo", + * ""); + * + * metaEl->addElement( new CppUnit::XmlElement("Author", m_author) ); + * metaEl->addElement( new CppUnit::XmlElement("Project", m_projectName) ); + * metaEl->addElement( new CppUnit::XmlElement("Date", szDate ) ); + * + * document->rootElement().addElement(metaEl); + * }; + * private: + * std::string m_projectName; + * std::string m_author; + * }; + * \endcode + * + * Within your application's main code, you need to snap the hook + * object into your xml outputter object like shown below: + * + * \code + * CppUnit::TextUi::TestRunner runner; + * std::ofstream outputFile("testResults.xml"); + * + * CppUnit::XmlOutputter* outputter = new CppUnit::XmlOutputter( &runner.result(), + * outputFile ); + * MyXmlOutputterHook hook("myProject", "meAuthor"); + * outputter->addHook(&hook); + * runner.setOutputter(outputter); + * runner.addTest( VectorFixture::suite() ); + * runner.run(); + * outputFile.close(); + * \endcode + * + * This results into the following output: + * + * \code + * + * + * meAuthor + * myProject + * 1028143912 + * + * + * ... + * \endcode + * + * \see XmlOutputter, CppUnitTestPlugIn. + */ +class CPPUNIT_API XmlOutputterHook +{ +public: + /*! Called before any elements is added to the root element. + * \param document XML Document being created. + */ + virtual void beginDocument( XmlDocument *document ); + + /*! Called after adding all elements to the root element. + * \param document XML Document being created. + */ + virtual void endDocument( XmlDocument *document ); + + /*! Called after adding a fail test element. + * \param document XML Document being created. + * \param testElement \ element. + * \param test Test that failed. + * \param failure Test failure data. + */ + virtual void failTestAdded( XmlDocument *document, + XmlElement *testElement, + Test *test, + TestFailure *failure ); + + /*! Called after adding a successful test element. + * \param document XML Document being created. + * \param testElement \ element. + * \param test Test that was successful. + */ + virtual void successfulTestAdded( XmlDocument *document, + XmlElement *testElement, + Test *test ); + + /*! Called after adding the statistic element. + * \param document XML Document being created. + * \param statisticsElement \ element. + */ + virtual void statisticsAdded( XmlDocument *document, + XmlElement *statisticsElement ); + + virtual ~XmlOutputterHook() {} +}; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_XMLOUTPUTTERHOOK_H diff --git a/3rdParty/include/cppunit/config-auto.h b/3rdParty/include/cppunit/config-auto.h new file mode 100644 index 0000000000..98708637a3 --- /dev/null +++ b/3rdParty/include/cppunit/config-auto.h @@ -0,0 +1,160 @@ +#ifndef _INCLUDE_CPPUNIT_CONFIG_AUTO_H +#define _INCLUDE_CPPUNIT_CONFIG_AUTO_H 1 + +/* include/cppunit/config-auto.h. Generated automatically at end of configure. */ +/* config/config.h. Generated from config.h.in by configure. */ +/* config/config.h.in. Generated from configure.in by autoheader. */ + +/* define if library uses std::string::compare(string,pos,n) */ +/* #undef CPPUNIT_FUNC_STRING_COMPARE_STRING_FIRST */ + +/* define if the library defines strstream */ +#ifndef CPPUNIT_HAVE_CLASS_STRSTREAM +#define CPPUNIT_HAVE_CLASS_STRSTREAM 1 +#endif + +/* Define to 1 if you have the header file. */ +#ifndef CPPUNIT_HAVE_CMATH +#define CPPUNIT_HAVE_CMATH 1 +#endif + +/* Define if you have the GNU dld library. */ +/* #undef CPPUNIT_HAVE_DLD */ + +/* Define to 1 if you have the `dlerror' function. */ +/* #undef CPPUNIT_HAVE_DLERROR */ + +/* Define to 1 if you have the header file. */ +/* #undef CPPUNIT_HAVE_DLFCN_H */ + +/* Define to 1 if you have the `finite' function. */ +#ifndef CPPUNIT_HAVE_FINITE +#define CPPUNIT_HAVE_FINITE 1 +#endif + +/* define if the compiler supports GCC C++ ABI name demangling */ +/* #undef CPPUNIT_HAVE_GCC_ABI_DEMANGLE */ + +/* Define to 1 if you have the header file. */ +#ifndef CPPUNIT_HAVE_INTTYPES_H +#define CPPUNIT_HAVE_INTTYPES_H 1 +#endif + +/* define if compiler has isfinite */ +#ifndef CPPUNIT_HAVE_ISFINITE +#define CPPUNIT_HAVE_ISFINITE 1 +#endif + +/* Define if you have the libdl library or equivalent. */ +/* #undef CPPUNIT_HAVE_LIBDL */ + +/* Define to 1 if you have the header file. */ +#ifndef CPPUNIT_HAVE_MEMORY_H +#define CPPUNIT_HAVE_MEMORY_H 1 +#endif + +/* define to 1 if the compiler implements namespaces */ +#ifndef CPPUNIT_HAVE_NAMESPACES +#define CPPUNIT_HAVE_NAMESPACES 1 +#endif + +/* define if the compiler supports Run-Time Type Identification */ +#ifndef CPPUNIT_HAVE_RTTI +#define CPPUNIT_HAVE_RTTI 1 +#endif + +/* Define if you have the shl_load function. */ +/* #undef CPPUNIT_HAVE_SHL_LOAD */ + +/* define if the compiler has stringstream */ +#ifndef CPPUNIT_HAVE_SSTREAM +#define CPPUNIT_HAVE_SSTREAM 1 +#endif + +/* Define to 1 if you have the header file. */ +#ifndef CPPUNIT_HAVE_STDINT_H +#define CPPUNIT_HAVE_STDINT_H 1 +#endif + +/* Define to 1 if you have the header file. */ +#ifndef CPPUNIT_HAVE_STDLIB_H +#define CPPUNIT_HAVE_STDLIB_H 1 +#endif + +/* Define to 1 if you have the header file. */ +#ifndef CPPUNIT_HAVE_STRINGS_H +#define CPPUNIT_HAVE_STRINGS_H 1 +#endif + +/* Define to 1 if you have the header file. */ +#ifndef CPPUNIT_HAVE_STRING_H +#define CPPUNIT_HAVE_STRING_H 1 +#endif + +/* Define to 1 if you have the header file. */ +#ifndef CPPUNIT_HAVE_STRSTREAM +#define CPPUNIT_HAVE_STRSTREAM 1 +#endif + +/* Define to 1 if you have the header file. */ +#ifndef CPPUNIT_HAVE_SYS_STAT_H +#define CPPUNIT_HAVE_SYS_STAT_H 1 +#endif + +/* Define to 1 if you have the header file. */ +#ifndef CPPUNIT_HAVE_SYS_TYPES_H +#define CPPUNIT_HAVE_SYS_TYPES_H 1 +#endif + +/* Define to 1 if you have the header file. */ +#ifndef CPPUNIT_HAVE_UNISTD_H +#define CPPUNIT_HAVE_UNISTD_H 1 +#endif + +/* Name of package */ +#ifndef CPPUNIT_PACKAGE +#define CPPUNIT_PACKAGE "cppunit" +#endif + +/* Define to the address where bug reports for this package should be sent. */ +#ifndef CPPUNIT_PACKAGE_BUGREPORT +#define CPPUNIT_PACKAGE_BUGREPORT "" +#endif + +/* Define to the full name of this package. */ +#ifndef CPPUNIT_PACKAGE_NAME +#define CPPUNIT_PACKAGE_NAME "" +#endif + +/* Define to the full name and version of this package. */ +#ifndef CPPUNIT_PACKAGE_STRING +#define CPPUNIT_PACKAGE_STRING "" +#endif + +/* Define to the one symbol short name of this package. */ +#ifndef CPPUNIT_PACKAGE_TARNAME +#define CPPUNIT_PACKAGE_TARNAME "" +#endif + +/* Define to the version of this package. */ +#ifndef CPPUNIT_PACKAGE_VERSION +#define CPPUNIT_PACKAGE_VERSION "" +#endif + +/* Define to 1 if you have the ANSI C header files. */ +#ifndef CPPUNIT_STDC_HEADERS +#define CPPUNIT_STDC_HEADERS 1 +#endif + +/* Define to 1 to use type_info::name() for class names */ +#ifndef CPPUNIT_USE_TYPEINFO_NAME +#define CPPUNIT_USE_TYPEINFO_NAME CPPUNIT_HAVE_RTTI +#endif + +/* Version number of package */ +#ifndef CPPUNIT_VERSION +#define CPPUNIT_VERSION "1.12.1" +#endif + +/* _INCLUDE_CPPUNIT_CONFIG_AUTO_H */ +#endif diff --git a/3rdParty/include/cppunit/config/CppUnitApi.h b/3rdParty/include/cppunit/config/CppUnitApi.h new file mode 100644 index 0000000000..a068bbd543 --- /dev/null +++ b/3rdParty/include/cppunit/config/CppUnitApi.h @@ -0,0 +1,33 @@ +#ifndef CPPUNIT_CONFIG_CPPUNITAPI +#define CPPUNIT_CONFIG_CPPUNITAPI + +#undef CPPUNIT_API + +#ifdef WIN32 + +// define CPPUNIT_DLL_BUILD when building CppUnit dll. +#ifdef CPPUNIT_BUILD_DLL +#define CPPUNIT_API __declspec(dllexport) +#endif + +// define CPPUNIT_DLL when linking to CppUnit dll. +#ifdef CPPUNIT_DLL +#define CPPUNIT_API __declspec(dllimport) +#endif + +#ifdef CPPUNIT_API +#undef CPPUNIT_NEED_DLL_DECL +#define CPPUNIT_NEED_DLL_DECL 1 +#endif + +#endif + + +#ifndef CPPUNIT_API +#define CPPUNIT_API +#undef CPPUNIT_NEED_DLL_DECL +#define CPPUNIT_NEED_DLL_DECL 0 +#endif + + +#endif // CPPUNIT_CONFIG_CPPUNITAPI diff --git a/3rdParty/include/cppunit/config/SelectDllLoader.h b/3rdParty/include/cppunit/config/SelectDllLoader.h new file mode 100644 index 0000000000..dc1c011558 --- /dev/null +++ b/3rdParty/include/cppunit/config/SelectDllLoader.h @@ -0,0 +1,76 @@ +#ifndef CPPUNIT_CONFIG_SELECTDLLLOADER_H +#define CPPUNIT_CONFIG_SELECTDLLLOADER_H + +/*! \file + * Selects DynamicLibraryManager implementation. + * + * Don't include this file directly. Include Portability.h instead. + */ + +/*! + * \def CPPUNIT_NO_TESTPLUGIN + * \brief If defined, then plug-in related classes and functions will not be compiled. + * + * \internal + * CPPUNIT_HAVE_WIN32_DLL_LOADER + * If defined, Win32 implementation of DynamicLibraryManager will be used. + * + * CPPUNIT_HAVE_BEOS_DLL_LOADER + * If defined, BeOs implementation of DynamicLibraryManager will be used. + * + * CPPUNIT_HAVE_UNIX_DLL_LOADER + * If defined, Unix implementation (dlfcn.h) of DynamicLibraryManager will be used. + */ + +/*! + * \def CPPUNIT_PLUGIN_EXPORT + * \ingroup WritingTestPlugIn + * \brief A macro to export a function from a dynamic library + * + * This macro export the C function following it from a dynamic library. + * Exporting the function makes it accessible to the DynamicLibraryManager. + * + * Example of usage: + * \code + * #include + * + * CPPUNIT_PLUGIN_EXPORT CppUnitTestPlugIn *CPPUNIT_PLUGIN_EXPORTED_NAME(void) + * { + * ... + * return &myPlugInInterface; + * } + * \endcode + */ + +#if !defined(CPPUNIT_NO_TESTPLUGIN) + +// Is WIN32 platform ? +#if defined(WIN32) +#define CPPUNIT_HAVE_WIN32_DLL_LOADER 1 +#undef CPPUNIT_PLUGIN_EXPORT +#define CPPUNIT_PLUGIN_EXPORT extern "C" __declspec(dllexport) + +// Is BeOS platform ? +#elif defined(__BEOS__) +#define CPPUNIT_HAVE_BEOS_DLL_LOADER 1 + +// Is Unix platform and have shl_load() (hp-ux) +#elif defined(CPPUNIT_HAVE_SHL_LOAD) +#define CPPUNIT_HAVE_UNIX_SHL_LOADER 1 + +// Is Unix platform and have include +#elif defined(CPPUNIT_HAVE_LIBDL) +#define CPPUNIT_HAVE_UNIX_DLL_LOADER 1 + +// Otherwise, disable support for DllLoader +#else +#define CPPUNIT_NO_TESTPLUGIN 1 +#endif + +#if !defined(CPPUNIT_PLUGIN_EXPORT) +#define CPPUNIT_PLUGIN_EXPORT extern "C" +#endif // !defined(CPPUNIT_PLUGIN_EXPORT) + +#endif // !defined(CPPUNIT_NO_TESTPLUGIN) + +#endif // CPPUNIT_CONFIG_SELECTDLLLOADER_H diff --git a/3rdParty/include/cppunit/config/SourcePrefix.h b/3rdParty/include/cppunit/config/SourcePrefix.h new file mode 100644 index 0000000000..2334601b59 --- /dev/null +++ b/3rdParty/include/cppunit/config/SourcePrefix.h @@ -0,0 +1,14 @@ +#ifndef CPPUNIT_CONFIG_H_INCLUDED +#define CPPUNIT_CONFIG_H_INCLUDED + +#include + +#ifdef _MSC_VER +#pragma warning(disable: 4018 4284 4146) +#if _MSC_VER >= 1400 +#pragma warning(disable: 4996) // sprintf is deprecated +#endif +#endif + + +#endif // CPPUNIT_CONFIG_H_INCLUDED diff --git a/3rdParty/include/cppunit/config/config-bcb5.h b/3rdParty/include/cppunit/config/config-bcb5.h new file mode 100644 index 0000000000..d491452062 --- /dev/null +++ b/3rdParty/include/cppunit/config/config-bcb5.h @@ -0,0 +1,47 @@ +#ifndef _INCLUDE_CPPUNIT_CONFIG_BCB5_H +#define _INCLUDE_CPPUNIT_CONFIG_BCB5_H 1 + +#define HAVE_CMATH 1 + +/* include/cppunit/config-bcb5.h. Manually adapted from + include/cppunit/config-auto.h */ + +/* define to 1 if the compiler implements namespaces */ +#ifndef CPPUNIT_HAVE_NAMESPACES +#define CPPUNIT_HAVE_NAMESPACES 1 +#endif + +/* define if library uses std::string::compare(string,pos,n) */ +#ifndef CPPUNIT_FUNC_STRING_COMPARE_STRING_FIRST +#define CPPUNIT_FUNC_STRING_COMPARE_STRING_FIRST 0 +#endif + +/* Define if you have the header file. */ +#ifdef CPPUNIT_HAVE_DLFCN_H +#undef CPPUNIT_HAVE_DLFCN_H +#endif + +/* define to 1 if the compiler implements namespaces */ +#ifndef CPPUNIT_HAVE_NAMESPACES +#define CPPUNIT_HAVE_NAMESPACES 1 +#endif + +/* define if the compiler supports Run-Time Type Identification */ +#ifndef CPPUNIT_HAVE_RTTI +#define CPPUNIT_HAVE_RTTI 1 +#endif + +/* Define to 1 to use type_info::name() for class names */ +#ifndef CPPUNIT_USE_TYPEINFO_NAME +#define CPPUNIT_USE_TYPEINFO_NAME CPPUNIT_HAVE_RTTI +#endif + +#define CPPUNIT_HAVE_SSTREAM 1 + +/* Name of package */ +#ifndef CPPUNIT_PACKAGE +#define CPPUNIT_PACKAGE "cppunit" +#endif + +/* _INCLUDE_CPPUNIT_CONFIG_BCB5_H */ +#endif diff --git a/3rdParty/include/cppunit/config/config-evc4.h b/3rdParty/include/cppunit/config/config-evc4.h new file mode 100644 index 0000000000..a79169812d --- /dev/null +++ b/3rdParty/include/cppunit/config/config-evc4.h @@ -0,0 +1,78 @@ +#ifndef _INCLUDE_CPPUNIT_CONFIG_EVC4_H +#define _INCLUDE_CPPUNIT_CONFIG_EVC4_H 1 + +#if _MSC_VER > 1000 // VC++ +#pragma warning( disable : 4786 ) // disable warning debug symbol > 255... +#endif // _MSC_VER > 1000 + +#define HAVE_CMATH 1 + +/* include/cppunit/config-msvc6.h. Manually adapted from + include/cppunit/config-auto.h */ + +/* define to 1 if the compiler implements namespaces */ +#ifndef CPPUNIT_HAVE_NAMESPACES +#define CPPUNIT_HAVE_NAMESPACES 1 +#endif + +/* define if library uses std::string::compare(string,pos,n) */ +#ifdef CPPUNIT_FUNC_STRING_COMPARE_STRING_FIRST +#undef CPPUNIT_FUNC_STRING_COMPARE_STRING_FIRST +#endif + +/* Define if you have the header file. */ +#ifdef CPPUNIT_HAVE_DLFCN_H +#undef CPPUNIT_HAVE_DLFCN_H +#endif + +/* define to 1 if the compiler implements namespaces */ +#ifndef CPPUNIT_HAVE_NAMESPACES +#define CPPUNIT_HAVE_NAMESPACES 1 +#endif + +/* define if the compiler supports Run-Time Type Identification */ +#ifndef CPPUNIT_HAVE_RTTI +#define CPPUNIT_HAVE_RTTI 0 +#endif + +/* Define to 1 to use type_info::name() for class names */ +#ifndef CPPUNIT_USE_TYPEINFO_NAME +#define CPPUNIT_USE_TYPEINFO_NAME CPPUNIT_HAVE_RTTI +#endif + +#define CPPUNIT_NO_STREAM 1 +#define CPPUNIT_NO_ASSERT 1 + +#define CPPUNIT_HAVE_SSTREAM 0 + +/* Name of package */ +#ifndef CPPUNIT_PACKAGE +#define CPPUNIT_PACKAGE "cppunit" +#endif + + +// Compiler error location format for CompilerOutputter +// See class CompilerOutputter for format. +#undef CPPUNIT_COMPILER_LOCATION_FORMAT +#if _MSC_VER >= 1300 // VS 7.0 +# define CPPUNIT_COMPILER_LOCATION_FORMAT "%p(%l) : error : " +#else +# define CPPUNIT_COMPILER_LOCATION_FORMAT "%p(%l):" +#endif + +/* define to 1 if the compiler has _finite() */ +#ifndef CPPUNIT_HAVE__FINITE +#define CPPUNIT_HAVE__FINITE 1 +#endif + +// Uncomment to turn on STL wrapping => use this to test compilation. +// This will make CppUnit subclass std::vector & co to provide default +// parameter. +/*#define CPPUNIT_STD_NEED_ALLOCATOR 1 +#define CPPUNIT_STD_ALLOCATOR std::allocator +//#define CPPUNIT_NO_NAMESPACE 1 +*/ + + +/* _INCLUDE_CPPUNIT_CONFIG_EVC4_H */ +#endif diff --git a/3rdParty/include/cppunit/config/config-mac.h b/3rdParty/include/cppunit/config/config-mac.h new file mode 100644 index 0000000000..4ace906cf9 --- /dev/null +++ b/3rdParty/include/cppunit/config/config-mac.h @@ -0,0 +1,58 @@ +#ifndef _INCLUDE_CPPUNIT_CONFIG_MAC_H +#define _INCLUDE_CPPUNIT_CONFIG_MAC_H 1 + +/* MacOS X should be installed using the configure script. + This file is for other macs. + + It is not integrated into because we don't + know a suitable preprocessor symbol that will distinguish MacOS X + from other MacOS versions. Email us if you know the answer. +*/ + +/* define if library uses std::string::compare(string,pos,n) */ +#ifdef CPPUNIT_FUNC_STRING_COMPARE_STRING_FIRST +#undef CPPUNIT_FUNC_STRING_COMPARE_STRING_FIRST +#endif + +/* define if the library defines strstream */ +#ifndef CPPUNIT_HAVE_CLASS_STRSTREAM +#define CPPUNIT_HAVE_CLASS_STRSTREAM 1 +#endif + +/* Define if you have the header file. */ +#ifdef CPPUNIT_HAVE_CMATH +#undef CPPUNIT_HAVE_CMATH +#endif + +/* Define if you have the header file. */ +#ifdef CPPUNIT_HAVE_DLFCN_H +#undef CPPUNIT_HAVE_DLFCN_H +#endif + +/* define to 1 if the compiler implements namespaces */ +#ifndef CPPUNIT_HAVE_NAMESPACES +#define CPPUNIT_HAVE_NAMESPACES 1 +#endif + +/* define if the compiler supports Run-Time Type Identification */ +#ifndef CPPUNIT_HAVE_RTTI +#define CPPUNIT_HAVE_RTTI 1 +#endif + +/* define if the compiler has stringstream */ +#ifndef CPPUNIT_HAVE_SSTREAM +#define CPPUNIT_HAVE_SSTREAM 1 +#endif + +/* Define if you have the header file. */ +#ifndef CPPUNIT_HAVE_STRSTREAM +#define CPPUNIT_HAVE_STRSTREAM 1 +#endif + +/* Define to 1 to use type_info::name() for class names */ +#ifndef CPPUNIT_USE_TYPEINFO_NAME +#define CPPUNIT_USE_TYPEINFO_NAME CPPUNIT_HAVE_RTTI +#endif + +/* _INCLUDE_CPPUNIT_CONFIG_MAC_H */ +#endif diff --git a/3rdParty/include/cppunit/config/config-msvc6.h b/3rdParty/include/cppunit/config/config-msvc6.h new file mode 100644 index 0000000000..d6881711ab --- /dev/null +++ b/3rdParty/include/cppunit/config/config-msvc6.h @@ -0,0 +1,83 @@ +#ifndef _INCLUDE_CPPUNIT_CONFIG_MSVC6_H +#define _INCLUDE_CPPUNIT_CONFIG_MSVC6_H 1 + +#if _MSC_VER > 1000 // VC++ +#pragma warning( disable : 4786 ) // disable warning debug symbol > 255... +#endif // _MSC_VER > 1000 + +#define HAVE_CMATH 1 + +/* include/cppunit/config-msvc6.h. Manually adapted from + include/cppunit/config-auto.h */ + +/* define to 1 if the compiler implements namespaces */ +#ifndef CPPUNIT_HAVE_NAMESPACES +#define CPPUNIT_HAVE_NAMESPACES 1 +#endif + +/* define if library uses std::string::compare(string,pos,n) */ +#ifdef CPPUNIT_FUNC_STRING_COMPARE_STRING_FIRST +#undef CPPUNIT_FUNC_STRING_COMPARE_STRING_FIRST +#endif + +/* Define if you have the header file. */ +#ifdef CPPUNIT_HAVE_DLFCN_H +#undef CPPUNIT_HAVE_DLFCN_H +#endif + +/* define to 1 if the compiler implements namespaces */ +#ifndef CPPUNIT_HAVE_NAMESPACES +#define CPPUNIT_HAVE_NAMESPACES 1 +#endif + +/* define if the compiler supports Run-Time Type Identification */ +#ifndef CPPUNIT_HAVE_RTTI +# ifdef _CPPRTTI // Defined by the compiler option /GR +# define CPPUNIT_HAVE_RTTI 1 +# else +# define CPPUNIT_HAVE_RTTI 0 +# endif +#endif + +/* Define to 1 to use type_info::name() for class names */ +#ifndef CPPUNIT_USE_TYPEINFO_NAME +#define CPPUNIT_USE_TYPEINFO_NAME CPPUNIT_HAVE_RTTI +#endif + +#define CPPUNIT_HAVE_SSTREAM 1 + +/* Name of package */ +#ifndef CPPUNIT_PACKAGE +#define CPPUNIT_PACKAGE "cppunit" +#endif + + +// Compiler error location format for CompilerOutputter +// See class CompilerOutputter for format. +#undef CPPUNIT_COMPILER_LOCATION_FORMAT +#if _MSC_VER >= 1300 // VS 7.0 +# define CPPUNIT_COMPILER_LOCATION_FORMAT "%p(%l) : error : " +#else +# define CPPUNIT_COMPILER_LOCATION_FORMAT "%p(%l):" +#endif + +// Define to 1 if the compiler support C++ style cast. +#define CPPUNIT_HAVE_CPP_CAST 1 + +/* define to 1 if the compiler has _finite() */ +#ifndef CPPUNIT_HAVE__FINITE +#define CPPUNIT_HAVE__FINITE 1 +#endif + + +// Uncomment to turn on STL wrapping => use this to test compilation. +// This will make CppUnit subclass std::vector & co to provide default +// parameter. +/*#define CPPUNIT_STD_NEED_ALLOCATOR 1 +#define CPPUNIT_STD_ALLOCATOR std::allocator +//#define CPPUNIT_NO_NAMESPACE 1 +*/ + + +/* _INCLUDE_CPPUNIT_CONFIG_MSVC6_H */ +#endif diff --git a/3rdParty/include/cppunit/extensions/AutoRegisterSuite.h b/3rdParty/include/cppunit/extensions/AutoRegisterSuite.h new file mode 100644 index 0000000000..e04adb5d93 --- /dev/null +++ b/3rdParty/include/cppunit/extensions/AutoRegisterSuite.h @@ -0,0 +1,83 @@ +#ifndef CPPUNIT_EXTENSIONS_AUTOREGISTERSUITE_H +#define CPPUNIT_EXTENSIONS_AUTOREGISTERSUITE_H + +#include +#include +#include + +CPPUNIT_NS_BEGIN + + +/*! \brief (Implementation) Automatically register the test suite of the specified type. + * + * You should not use this class directly. Instead, use the following macros: + * - CPPUNIT_TEST_SUITE_REGISTRATION() + * - CPPUNIT_TEST_SUITE_NAMED_REGISTRATION() + * + * This object will register the test returned by TestCaseType::suite() + * when constructed to the test registry. + * + * This object is intented to be used as a static variable. + * + * + * \param TestCaseType Type of the test case which suite is registered. + * \see CPPUNIT_TEST_SUITE_REGISTRATION, CPPUNIT_TEST_SUITE_NAMED_REGISTRATION + * \see CppUnit::TestFactoryRegistry. + */ +template +class AutoRegisterSuite +{ +public: + /** Auto-register the suite factory in the global registry. + */ + AutoRegisterSuite() + : m_registry( &TestFactoryRegistry::getRegistry() ) + { + m_registry->registerFactory( &m_factory ); + } + + /** Auto-register the suite factory in the specified registry. + * \param name Name of the registry. + */ + AutoRegisterSuite( const std::string &name ) + : m_registry( &TestFactoryRegistry::getRegistry( name ) ) + { + m_registry->registerFactory( &m_factory ); + } + + ~AutoRegisterSuite() + { + if ( TestFactoryRegistry::isValid() ) + m_registry->unregisterFactory( &m_factory ); + } + +private: + TestFactoryRegistry *m_registry; + TestSuiteFactory m_factory; +}; + + +/*! \brief (Implementation) Automatically adds a registry into another registry. + * + * Don't use this class. Use the macros CPPUNIT_REGISTRY_ADD() and + * CPPUNIT_REGISTRY_ADD_TO_DEFAULT() instead. + */ +class AutoRegisterRegistry +{ +public: + AutoRegisterRegistry( const std::string &which, + const std::string &to ) + { + TestFactoryRegistry::getRegistry( to ).addRegistry( which ); + } + + AutoRegisterRegistry( const std::string &which ) + { + TestFactoryRegistry::getRegistry().addRegistry( which ); + } +}; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_EXTENSIONS_AUTOREGISTERSUITE_H diff --git a/3rdParty/include/cppunit/extensions/ExceptionTestCaseDecorator.h b/3rdParty/include/cppunit/extensions/ExceptionTestCaseDecorator.h new file mode 100644 index 0000000000..9c816addb3 --- /dev/null +++ b/3rdParty/include/cppunit/extensions/ExceptionTestCaseDecorator.h @@ -0,0 +1,104 @@ +#ifndef CPPUNIT_EXTENSIONS_EXCEPTIONTESTCASEDECORATOR_H +#define CPPUNIT_EXTENSIONS_EXCEPTIONTESTCASEDECORATOR_H + +#include +#include +#include + +CPPUNIT_NS_BEGIN + + +/*! \brief Expected exception test case decorator. + * + * A decorator used to assert that a specific test case should throw an + * exception of a given type. + * + * You should use this class only if you need to check the exception object + * state (that a specific cause is set for example). If you don't need to + * do that, you might consider using CPPUNIT_TEST_EXCEPTION() instead. + * + * Intended use is to subclass and override checkException(). Example: + * + * \code + * + * class NetworkErrorTestCaseDecorator : + * public ExceptionTestCaseDecorator + * { + * public: + * NetworkErrorTestCaseDecorator( NetworkError::Cause expectedCause ) + * : m_expectedCause( expectedCause ) + * { + * } + * private: + * void checkException( ExpectedExceptionType &e ) + * { + * CPPUNIT_ASSERT_EQUAL( m_expectedCause, e.getCause() ); + * } + * + * NetworkError::Cause m_expectedCause; + * }; + * \endcode + * + */ +template +class ExceptionTestCaseDecorator : public TestCaseDecorator +{ +public: + typedef ExpectedException ExpectedExceptionType; + + /*! \brief Decorates the specified test. + * \param test TestCase to decorate. Assumes ownership of the test. + */ + ExceptionTestCaseDecorator( TestCase *test ) + : TestCaseDecorator( test ) + { + } + + /*! \brief Checks that the expected exception is thrown by the decorated test. + * is thrown. + * + * Calls the decorated test runTest() and checks that an exception of + * type ExpectedException is thrown. Call checkException() passing the + * exception that was caught so that some assertions can be made if + * needed. + */ + void runTest() + { + try + { + TestCaseDecorator::runTest(); + } + catch ( ExpectedExceptionType &e ) + { + checkException( e ); + return; + } + + // Moved outside the try{} statement to handle the case where the + // expected exception type is Exception (expecting assertion failure). +#if CPPUNIT_USE_TYPEINFO_NAME + throw Exception( Message( + "expected exception not thrown", + "Expected exception type: " + + TypeInfoHelper::getClassName( + typeid( ExpectedExceptionType ) ) ) ); +#else + throw Exception( Message("expected exception not thrown") ); +#endif + } + +private: + /*! \brief Called when the exception is caught. + * + * Should be overriden to check the exception. + */ + virtual void checkException( ExpectedExceptionType &e ) + { + } +}; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_EXTENSIONS_EXCEPTIONTESTCASEDECORATOR_H + diff --git a/3rdParty/include/cppunit/extensions/HelperMacros.h b/3rdParty/include/cppunit/extensions/HelperMacros.h new file mode 100644 index 0000000000..12431e4657 --- /dev/null +++ b/3rdParty/include/cppunit/extensions/HelperMacros.h @@ -0,0 +1,541 @@ +// ////////////////////////////////////////////////////////////////////////// +// Header file HelperMacros.h +// (c)Copyright 2000, Baptiste Lepilleur. +// Created: 2001/04/15 +// ////////////////////////////////////////////////////////////////////////// +#ifndef CPPUNIT_EXTENSIONS_HELPERMACROS_H +#define CPPUNIT_EXTENSIONS_HELPERMACROS_H + +#include +#include +#include +#include +#include +#include +#include +#include + + +/*! \addtogroup WritingTestFixture Writing test fixture + */ +/** @{ + */ + + +/** \file + * Macros intended to ease the definition of test suites. + * + * The macros + * CPPUNIT_TEST_SUITE(), CPPUNIT_TEST(), and CPPUNIT_TEST_SUITE_END() + * are designed to facilitate easy creation of a test suite. + * For example, + * + * \code + * #include + * class MyTest : public CppUnit::TestFixture { + * CPPUNIT_TEST_SUITE( MyTest ); + * CPPUNIT_TEST( testEquality ); + * CPPUNIT_TEST( testSetName ); + * CPPUNIT_TEST_SUITE_END(); + * public: + * void testEquality(); + * void testSetName(); + * }; + * \endcode + * + * The effect of these macros is to define two methods in the + * class MyTest. The first method is an auxiliary function + * named registerTests that you will not need to call directly. + * The second function + * \code static CppUnit::TestSuite *suite()\endcode + * returns a pointer to the suite of tests defined by the CPPUNIT_TEST() + * macros. + * + * Rather than invoking suite() directly, + * the macro CPPUNIT_TEST_SUITE_REGISTRATION() is + * used to create a static variable that automatically + * registers its test suite in a global registry. + * The registry yields a Test instance containing all the + * registered suites. + * \code + * CPPUNIT_TEST_SUITE_REGISTRATION( MyTest ); + * CppUnit::Test* tp = + * CppUnit::TestFactoryRegistry::getRegistry().makeTest(); + * \endcode + * + * The test suite macros can even be used with templated test classes. + * For example: + * + * \code + * template + * class StringTest : public CppUnit::TestFixture { + * CPPUNIT_TEST_SUITE( StringTest ); + * CPPUNIT_TEST( testAppend ); + * CPPUNIT_TEST_SUITE_END(); + * public: + * ... + * }; + * \endcode + * + * You need to add in an implementation file: + * + * \code + * CPPUNIT_TEST_SUITE_REGISTRATION( StringTest ); + * CPPUNIT_TEST_SUITE_REGISTRATION( StringTest ); + * \endcode + */ + + +/*! \brief Begin test suite + * + * This macro starts the declaration of a new test suite. + * Use CPPUNIT_TEST_SUB_SUITE() instead, if you wish to include the + * test suite of the parent class. + * + * \param ATestFixtureType Type of the test case class. This type \b MUST + * be derived from TestFixture. + * \see CPPUNIT_TEST_SUB_SUITE, CPPUNIT_TEST, CPPUNIT_TEST_SUITE_END, + * \see CPPUNIT_TEST_SUITE_REGISTRATION, CPPUNIT_TEST_EXCEPTION, CPPUNIT_TEST_FAIL. + */ +#define CPPUNIT_TEST_SUITE( ATestFixtureType ) \ + public: \ + typedef ATestFixtureType TestFixtureType; \ + \ + private: \ + static const CPPUNIT_NS::TestNamer &getTestNamer__() \ + { \ + static CPPUNIT_TESTNAMER_DECL( testNamer, ATestFixtureType ); \ + return testNamer; \ + } \ + \ + public: \ + typedef CPPUNIT_NS::TestSuiteBuilderContext \ + TestSuiteBuilderContextType; \ + \ + static void \ + addTestsToSuite( CPPUNIT_NS::TestSuiteBuilderContextBase &baseContext ) \ + { \ + TestSuiteBuilderContextType context( baseContext ) + + +/*! \brief Begin test suite (includes parent suite) + * + * This macro may only be used in a class whose parent class + * defines a test suite using CPPUNIT_TEST_SUITE() or CPPUNIT_TEST_SUB_SUITE(). + * + * This macro begins the declaration of a test suite, in the same + * manner as CPPUNIT_TEST_SUITE(). In addition, the test suite of the + * parent is automatically inserted in the test suite being + * defined. + * + * Here is an example: + * + * \code + * #include + * class MySubTest : public MyTest { + * CPPUNIT_TEST_SUB_SUITE( MySubTest, MyTest ); + * CPPUNIT_TEST( testAdd ); + * CPPUNIT_TEST( testSub ); + * CPPUNIT_TEST_SUITE_END(); + * public: + * void testAdd(); + * void testSub(); + * }; + * \endcode + * + * \param ATestFixtureType Type of the test case class. This type \b MUST + * be derived from TestFixture. + * \param ASuperClass Type of the parent class. + * \see CPPUNIT_TEST_SUITE. + */ +#define CPPUNIT_TEST_SUB_SUITE( ATestFixtureType, ASuperClass ) \ + public: \ + typedef ASuperClass ParentTestFixtureType; \ + private: \ + CPPUNIT_TEST_SUITE( ATestFixtureType ); \ + ParentTestFixtureType::addTestsToSuite( baseContext ) + + +/*! \brief End declaration of the test suite. + * + * After this macro, member access is set to "private". + * + * \see CPPUNIT_TEST_SUITE. + * \see CPPUNIT_TEST_SUITE_REGISTRATION. + */ +#define CPPUNIT_TEST_SUITE_END() \ + } \ + \ + static CPPUNIT_NS::TestSuite *suite() \ + { \ + const CPPUNIT_NS::TestNamer &namer = getTestNamer__(); \ + std::auto_ptr suite( \ + new CPPUNIT_NS::TestSuite( namer.getFixtureName() )); \ + CPPUNIT_NS::ConcretTestFixtureFactory factory; \ + CPPUNIT_NS::TestSuiteBuilderContextBase context( *suite.get(), \ + namer, \ + factory ); \ + TestFixtureType::addTestsToSuite( context ); \ + return suite.release(); \ + } \ + private: /* dummy typedef so that the macro can still end with ';'*/ \ + typedef int CppUnitDummyTypedefForSemiColonEnding__ + +/*! \brief End declaration of an abstract test suite. + * + * Use this macro to indicate that the %TestFixture is abstract. No + * static suite() method will be declared. + * + * After this macro, member access is set to "private". + * + * Here is an example of usage: + * + * The abstract test fixture: + * \code + * #include + * class AbstractDocument; + * class AbstractDocumentTest : public CppUnit::TestFixture { + * CPPUNIT_TEST_SUITE( AbstractDocumentTest ); + * CPPUNIT_TEST( testInsertText ); + * CPPUNIT_TEST_SUITE_END_ABSTRACT(); + * public: + * void testInsertText(); + * + * void setUp() + * { + * m_document = makeDocument(); + * } + * + * void tearDown() + * { + * delete m_document; + * } + * protected: + * virtual AbstractDocument *makeDocument() =0; + * + * AbstractDocument *m_document; + * };\endcode + * + * The concret test fixture: + * \code + * class RichTextDocumentTest : public AbstractDocumentTest { + * CPPUNIT_TEST_SUB_SUITE( RichTextDocumentTest, AbstractDocumentTest ); + * CPPUNIT_TEST( testInsertFormatedText ); + * CPPUNIT_TEST_SUITE_END(); + * public: + * void testInsertFormatedText(); + * protected: + * AbstractDocument *makeDocument() + * { + * return new RichTextDocument(); + * } + * };\endcode + * + * \see CPPUNIT_TEST_SUB_SUITE. + * \see CPPUNIT_TEST_SUITE_REGISTRATION. + */ +#define CPPUNIT_TEST_SUITE_END_ABSTRACT() \ + } \ + private: /* dummy typedef so that the macro can still end with ';'*/ \ + typedef int CppUnitDummyTypedefForSemiColonEnding__ + + +/*! \brief Add a test to the suite (for custom test macro). + * + * The specified test will be added to the test suite being declared. This macro + * is intended for \e advanced usage, to extend %CppUnit by creating new macro such + * as CPPUNIT_TEST_EXCEPTION()... + * + * Between macro CPPUNIT_TEST_SUITE() and CPPUNIT_TEST_SUITE_END(), you can assume + * that the following variables can be used: + * \code + * typedef TestSuiteBuilder TestSuiteBuilderType; + * TestSuiteBuilderType &context; + * \endcode + * + * \c context can be used to name test case, create new test fixture instance, + * or add test case to the test fixture suite. + * + * Below is an example that show how to use this macro to create new macro to add + * test to the fixture suite. The macro below show how you would add a new type + * of test case which fails if the execution last more than a given time limit. + * It relies on an imaginary TimeOutTestCaller class which has an interface similar + * to TestCaller. + * + * \code + * #define CPPUNITEX_TEST_TIMELIMIT( testMethod, timeLimit ) \ + * CPPUNIT_TEST_SUITE_ADD_TEST( (new TimeOutTestCaller( \ + * namer.getTestNameFor( #testMethod ), \ + * &TestFixtureType::testMethod, \ + * factory.makeFixture(), \ + * timeLimit ) ) ) + * + * class PerformanceTest : CppUnit::TestFixture + * { + * public: + * CPPUNIT_TEST_SUITE( PerformanceTest ); + * CPPUNITEX_TEST_TIMELIMIT( testSortReverseOrder, 5.0 ); + * CPPUNIT_TEST_SUITE_END(); + * + * void testSortReverseOrder(); + * }; + * \endcode + * + * \param test Test to add to the suite. Must be a subclass of Test. The test name + * should have been obtained using TestNamer::getTestNameFor(). + */ +#define CPPUNIT_TEST_SUITE_ADD_TEST( test ) \ + context.addTest( test ) + +/*! \brief Add a method to the suite. + * \param testMethod Name of the method of the test case to add to the + * suite. The signature of the method must be of + * type: void testMethod(); + * \see CPPUNIT_TEST_SUITE. + */ +#define CPPUNIT_TEST( testMethod ) \ + CPPUNIT_TEST_SUITE_ADD_TEST( \ + ( new CPPUNIT_NS::TestCaller( \ + context.getTestNameFor( #testMethod), \ + &TestFixtureType::testMethod, \ + context.makeFixture() ) ) ) + +/*! \brief Add a test which fail if the specified exception is not caught. + * + * Example: + * \code + * #include + * #include + * class MyTest : public CppUnit::TestFixture { + * CPPUNIT_TEST_SUITE( MyTest ); + * CPPUNIT_TEST_EXCEPTION( testVectorAtThrow, std::invalid_argument ); + * CPPUNIT_TEST_SUITE_END(); + * public: + * void testVectorAtThrow() + * { + * std::vector v; + * v.at( 1 ); // must throw exception std::invalid_argument + * } + * }; + * \endcode + * + * \param testMethod Name of the method of the test case to add to the suite. + * \param ExceptionType Type of the exception that must be thrown by the test + * method. + * \deprecated Use the assertion macro CPPUNIT_ASSERT_THROW instead. + */ +#define CPPUNIT_TEST_EXCEPTION( testMethod, ExceptionType ) \ + CPPUNIT_TEST_SUITE_ADD_TEST( \ + (new CPPUNIT_NS::ExceptionTestCaseDecorator< ExceptionType >( \ + new CPPUNIT_NS::TestCaller< TestFixtureType >( \ + context.getTestNameFor( #testMethod ), \ + &TestFixtureType::testMethod, \ + context.makeFixture() ) ) ) ) + +/*! \brief Adds a test case which is excepted to fail. + * + * The added test case expect an assertion to fail. You usually used that type + * of test case when testing custom assertion macros. + * + * \code + * CPPUNIT_TEST_FAIL( testAssertFalseFail ); + * + * void testAssertFalseFail() + * { + * CPPUNIT_ASSERT( false ); + * } + * \endcode + * \see CreatingNewAssertions. + * \deprecated Use the assertion macro CPPUNIT_ASSERT_ASSERTION_FAIL instead. + */ +#define CPPUNIT_TEST_FAIL( testMethod ) \ + CPPUNIT_TEST_EXCEPTION( testMethod, CPPUNIT_NS::Exception ) + +/*! \brief Adds some custom test cases. + * + * Use this to add one or more test cases to the fixture suite. The specified + * method is called with a context parameter that can be used to name, + * instantiate fixture, and add instantiated test case to the fixture suite. + * The specified method must have the following signature: + * \code + * static void aMethodName( TestSuiteBuilderContextType &context ); + * \endcode + * + * \c TestSuiteBuilderContextType is typedef to + * TestSuiteBuilderContext declared by CPPUNIT_TEST_SUITE(). + * + * Here is an example that add two custom tests: + * + * \code + * #include + * + * class MyTest : public CppUnit::TestFixture { + * CPPUNIT_TEST_SUITE( MyTest ); + * CPPUNIT_TEST_SUITE_ADD_CUSTOM_TESTS( addTimeOutTests ); + * CPPUNIT_TEST_SUITE_END(); + * public: + * static void addTimeOutTests( TestSuiteBuilderContextType &context ) + * { + * context.addTest( new TimeOutTestCaller( context.getTestNameFor( "test1" ) ), + * &MyTest::test1, + * context.makeFixture(), + * 5.0 ); + * context.addTest( new TimeOutTestCaller( context.getTestNameFor( "test2" ) ), + * &MyTest::test2, + * context.makeFixture(), + * 5.0 ); + * } + * + * void test1() + * { + * // Do some test that may never end... + * } + * + * void test2() + * { + * // Do some test that may never end... + * } + * }; + * \endcode + * @param testAdderMethod Name of the method called to add the test cases. + */ +#define CPPUNIT_TEST_SUITE_ADD_CUSTOM_TESTS( testAdderMethod ) \ + testAdderMethod( context ) + +/*! \brief Adds a property to the test suite builder context. + * \param APropertyKey Key of the property to add. + * \param APropertyValue Value for the added property. + * Example: + * \code + * CPPUNIT_TEST_SUITE_PROPERTY("XmlFileName", "paraTest.xml"); \endcode + */ +#define CPPUNIT_TEST_SUITE_PROPERTY( APropertyKey, APropertyValue ) \ + context.addProperty( std::string(APropertyKey), \ + std::string(APropertyValue) ) + +/** @} + */ + + +/*! Adds the specified fixture suite to the unnamed registry. + * \ingroup CreatingTestSuite + * + * This macro declares a static variable whose construction + * causes a test suite factory to be inserted in a global registry + * of such factories. The registry is available by calling + * the static function CppUnit::TestFactoryRegistry::getRegistry(). + * + * \param ATestFixtureType Type of the test case class. + * \warning This macro should be used only once per line of code (the line + * number is used to name a hidden static variable). + * \see CPPUNIT_TEST_SUITE_NAMED_REGISTRATION + * \see CPPUNIT_REGISTRY_ADD_TO_DEFAULT + * \see CPPUNIT_REGISTRY_ADD + * \see CPPUNIT_TEST_SUITE, CppUnit::AutoRegisterSuite, + * CppUnit::TestFactoryRegistry. + */ +#define CPPUNIT_TEST_SUITE_REGISTRATION( ATestFixtureType ) \ + static CPPUNIT_NS::AutoRegisterSuite< ATestFixtureType > \ + CPPUNIT_MAKE_UNIQUE_NAME(autoRegisterRegistry__ ) + + +/** Adds the specified fixture suite to the specified registry suite. + * \ingroup CreatingTestSuite + * + * This macro declares a static variable whose construction + * causes a test suite factory to be inserted in the global registry + * suite of the specified name. The registry is available by calling + * the static function CppUnit::TestFactoryRegistry::getRegistry(). + * + * For the suite name, use a string returned by a static function rather + * than a hardcoded string. That way, you can know what are the name of + * named registry and you don't risk mistyping the registry name. + * + * \code + * // MySuites.h + * namespace MySuites { + * std::string math() { + * return "Math"; + * } + * } + * + * // ComplexNumberTest.cpp + * #include "MySuites.h" + * + * CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( ComplexNumberTest, MySuites::math() ); + * \endcode + * + * \param ATestFixtureType Type of the test case class. + * \param suiteName Name of the global registry suite the test suite is + * registered into. + * \warning This macro should be used only once per line of code (the line + * number is used to name a hidden static variable). + * \see CPPUNIT_TEST_SUITE_REGISTRATION + * \see CPPUNIT_REGISTRY_ADD_TO_DEFAULT + * \see CPPUNIT_REGISTRY_ADD + * \see CPPUNIT_TEST_SUITE, CppUnit::AutoRegisterSuite, + * CppUnit::TestFactoryRegistry.. + */ +#define CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( ATestFixtureType, suiteName ) \ + static CPPUNIT_NS::AutoRegisterSuite< ATestFixtureType > \ + CPPUNIT_MAKE_UNIQUE_NAME(autoRegisterRegistry__ )(suiteName) + +/*! Adds that the specified registry suite to another registry suite. + * \ingroup CreatingTestSuite + * + * Use this macros to automatically create test registry suite hierarchy. For example, + * if you want to create the following hierarchy: + * - Math + * - IntegerMath + * - FloatMath + * - FastFloat + * - StandardFloat + * + * You can do this automatically with: + * \code + * CPPUNIT_REGISTRY_ADD( "FastFloat", "FloatMath" ); + * CPPUNIT_REGISTRY_ADD( "IntegerMath", "Math" ); + * CPPUNIT_REGISTRY_ADD( "FloatMath", "Math" ); + * CPPUNIT_REGISTRY_ADD( "StandardFloat", "FloatMath" ); + * \endcode + * + * There is no specific order of declaration. Think of it as declaring links. + * + * You register the test in each suite using CPPUNIT_TEST_SUITE_NAMED_REGISTRATION. + * + * \param which Name of the registry suite to add to the registry suite named \a to. + * \param to Name of the registry suite \a which is added to. + * \see CPPUNIT_REGISTRY_ADD_TO_DEFAULT, CPPUNIT_TEST_SUITE_NAMED_REGISTRATION. + */ +#define CPPUNIT_REGISTRY_ADD( which, to ) \ + static CPPUNIT_NS::AutoRegisterRegistry \ + CPPUNIT_MAKE_UNIQUE_NAME( autoRegisterRegistry__ )( which, to ) + +/*! Adds that the specified registry suite to the default registry suite. + * \ingroup CreatingTestSuite + * + * This macro is just like CPPUNIT_REGISTRY_ADD except the specified registry + * suite is added to the default suite (root suite). + * + * \param which Name of the registry suite to add to the default registry suite. + * \see CPPUNIT_REGISTRY_ADD. + */ +#define CPPUNIT_REGISTRY_ADD_TO_DEFAULT( which ) \ + static CPPUNIT_NS::AutoRegisterRegistry \ + CPPUNIT_MAKE_UNIQUE_NAME( autoRegisterRegistry__ )( which ) + +// Backwards compatibility +// (Not tested!) + +#if CPPUNIT_ENABLE_CU_TEST_MACROS + +#define CU_TEST_SUITE(tc) CPPUNIT_TEST_SUITE(tc) +#define CU_TEST_SUB_SUITE(tc,sc) CPPUNIT_TEST_SUB_SUITE(tc,sc) +#define CU_TEST(tm) CPPUNIT_TEST(tm) +#define CU_TEST_SUITE_END() CPPUNIT_TEST_SUITE_END() +#define CU_TEST_SUITE_REGISTRATION(tc) CPPUNIT_TEST_SUITE_REGISTRATION(tc) + +#endif + + +#endif // CPPUNIT_EXTENSIONS_HELPERMACROS_H diff --git a/3rdParty/include/cppunit/extensions/Orthodox.h b/3rdParty/include/cppunit/extensions/Orthodox.h new file mode 100644 index 0000000000..722125937e --- /dev/null +++ b/3rdParty/include/cppunit/extensions/Orthodox.h @@ -0,0 +1,95 @@ +#ifndef CPPUNIT_EXTENSIONS_ORTHODOX_H +#define CPPUNIT_EXTENSIONS_ORTHODOX_H + +#include + +CPPUNIT_NS_BEGIN + + +/* + * Orthodox performs a simple set of tests on an arbitary + * class to make sure that it supports at least the + * following operations: + * + * default construction - constructor + * equality/inequality - operator== && operator!= + * assignment - operator= + * negation - operator! + * safe passage - copy construction + * + * If operations for each of these are not declared + * the template will not instantiate. If it does + * instantiate, tests are performed to make sure + * that the operations have correct semantics. + * + * Adding an orthodox test to a suite is very + * easy: + * + * public: Test *suite () { + * TestSuite *suiteOfTests = new TestSuite; + * suiteOfTests->addTest (new ComplexNumberTest ("testAdd"); + * suiteOfTests->addTest (new TestCaller > ()); + * return suiteOfTests; + * } + * + * Templated test cases be very useful when you are want to + * make sure that a group of classes have the same form. + * + * see TestSuite + */ + + +template class Orthodox : public TestCase +{ +public: + Orthodox () : TestCase ("Orthodox") {} + +protected: + ClassUnderTest call (ClassUnderTest object); + void runTest (); + + +}; + + +// Run an orthodoxy test +template void Orthodox::runTest () +{ + // make sure we have a default constructor + ClassUnderTest a, b, c; + + // make sure we have an equality operator + CPPUNIT_ASSERT (a == b); + + // check the inverse + b.operator= (a.operator! ()); + CPPUNIT_ASSERT (a != b); + + // double inversion + b = !!a; + CPPUNIT_ASSERT (a == b); + + // invert again + b = !a; + + // check calls + c = a; + CPPUNIT_ASSERT (c == call (a)); + + c = b; + CPPUNIT_ASSERT (c == call (b)); + +} + + +// Exercise a call +template +ClassUnderTest Orthodox::call (ClassUnderTest object) +{ + return object; +} + + +CPPUNIT_NS_END + +#endif diff --git a/3rdParty/include/cppunit/extensions/RepeatedTest.h b/3rdParty/include/cppunit/extensions/RepeatedTest.h new file mode 100644 index 0000000000..390ce4807b --- /dev/null +++ b/3rdParty/include/cppunit/extensions/RepeatedTest.h @@ -0,0 +1,43 @@ +#ifndef CPPUNIT_EXTENSIONS_REPEATEDTEST_H +#define CPPUNIT_EXTENSIONS_REPEATEDTEST_H + +#include +#include + +CPPUNIT_NS_BEGIN + + +class Test; +class TestResult; + + +/*! \brief Decorator that runs a test repeatedly. + * + * Does not assume ownership of the test it decorates + */ +class CPPUNIT_API RepeatedTest : public TestDecorator +{ +public: + RepeatedTest( Test *test, + int timesRepeat ) : + TestDecorator( test ), + m_timesRepeat(timesRepeat) + { + } + + void run( TestResult *result ); + + int countTestCases() const; + +private: + RepeatedTest( const RepeatedTest & ); + void operator=( const RepeatedTest & ); + + const int m_timesRepeat; +}; + + +CPPUNIT_NS_END + + +#endif // CPPUNIT_EXTENSIONS_REPEATEDTEST_H diff --git a/3rdParty/include/cppunit/extensions/TestCaseDecorator.h b/3rdParty/include/cppunit/extensions/TestCaseDecorator.h new file mode 100644 index 0000000000..3a15ba9747 --- /dev/null +++ b/3rdParty/include/cppunit/extensions/TestCaseDecorator.h @@ -0,0 +1,40 @@ +#ifndef CPPUNIT_EXTENSIONS_TESTCASEDECORATOR_H +#define CPPUNIT_EXTENSIONS_TESTCASEDECORATOR_H + +#include +#include + +CPPUNIT_NS_BEGIN + + +/*! \brief Decorator for Test cases. + * + * TestCaseDecorator provides an alternate means to extend functionality + * of a test class without subclassing the test. Instead, one can + * subclass the decorater and use it to wrap the test class. + * + * Does not assume ownership of the test it decorates + */ +class CPPUNIT_API TestCaseDecorator : public TestCase +{ +public: + TestCaseDecorator( TestCase *test ); + ~TestCaseDecorator(); + + std::string getName() const; + + void setUp(); + + void tearDown(); + + void runTest(); + +protected: + TestCase *m_test; +}; + + +CPPUNIT_NS_END + +#endif + diff --git a/3rdParty/include/cppunit/extensions/TestDecorator.h b/3rdParty/include/cppunit/extensions/TestDecorator.h new file mode 100644 index 0000000000..59d9a302e5 --- /dev/null +++ b/3rdParty/include/cppunit/extensions/TestDecorator.h @@ -0,0 +1,49 @@ +#ifndef CPPUNIT_EXTENSIONS_TESTDECORATOR_H +#define CPPUNIT_EXTENSIONS_TESTDECORATOR_H + +#include +#include + +CPPUNIT_NS_BEGIN + + +class TestResult; + + +/*! \brief Decorator for Tests. + * + * TestDecorator provides an alternate means to extend functionality + * of a test class without subclassing the test. Instead, one can + * subclass the decorater and use it to wrap the test class. + * + * Does not assume ownership of the test it decorates + */ +class CPPUNIT_API TestDecorator : public Test +{ +public: + TestDecorator( Test *test ); + ~TestDecorator(); + + int countTestCases() const; + + std::string getName() const; + + void run( TestResult *result ); + + int getChildTestCount() const; + +protected: + Test *doGetChildTestAt( int index ) const; + + Test *m_test; + +private: + TestDecorator( const TestDecorator &); + void operator =( const TestDecorator & ); +}; + + +CPPUNIT_NS_END + +#endif + diff --git a/3rdParty/include/cppunit/extensions/TestFactory.h b/3rdParty/include/cppunit/extensions/TestFactory.h new file mode 100644 index 0000000000..214d353490 --- /dev/null +++ b/3rdParty/include/cppunit/extensions/TestFactory.h @@ -0,0 +1,27 @@ +#ifndef CPPUNIT_EXTENSIONS_TESTFACTORY_H +#define CPPUNIT_EXTENSIONS_TESTFACTORY_H + +#include + +CPPUNIT_NS_BEGIN + + +class Test; + +/*! \brief Abstract Test factory. + */ +class CPPUNIT_API TestFactory +{ +public: + virtual ~TestFactory() {} + + /*! Makes a new test. + * \return A new Test. + */ + virtual Test* makeTest() = 0; +}; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_EXTENSIONS_TESTFACTORY_H diff --git a/3rdParty/include/cppunit/extensions/TestFactoryRegistry.h b/3rdParty/include/cppunit/extensions/TestFactoryRegistry.h new file mode 100644 index 0000000000..fc8723e400 --- /dev/null +++ b/3rdParty/include/cppunit/extensions/TestFactoryRegistry.h @@ -0,0 +1,182 @@ +#ifndef CPPUNIT_EXTENSIONS_TESTFACTORYREGISTRY_H +#define CPPUNIT_EXTENSIONS_TESTFACTORYREGISTRY_H + +#include + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( push ) +#pragma warning( disable: 4251) // X needs to have dll-interface to be used by clients of class Z +#endif + +#include +#include +#include + +CPPUNIT_NS_BEGIN + + +class TestSuite; + +#if CPPUNIT_NEED_DLL_DECL +// template class CPPUNIT_API std::set; +#endif + + +/*! \brief Registry for TestFactory. + * \ingroup CreatingTestSuite + * + * Notes that the registry \b DON'T assumes lifetime control for any registered tests + * anymore. + * + * The default registry is the registry returned by getRegistry() with the + * default name parameter value. + * + * To register tests, use the macros: + * - CPPUNIT_TEST_SUITE_REGISTRATION(): to add tests in the default registry. + * - CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(): to add tests in a named registry. + * + * Example 1: retreiving a suite that contains all the test registered with + * CPPUNIT_TEST_SUITE_REGISTRATION(). + * \code + * CppUnit::TestFactoryRegistry ®istry = CppUnit::TestFactoryRegistry::getRegistry(); + * CppUnit::TestSuite *suite = registry.makeTest(); + * \endcode + * + * Example 2: retreiving a suite that contains all the test registered with + * \link CPPUNIT_TEST_SUITE_NAMED_REGISTRATION() CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( ..., "Math" )\endlink. + * \code + * CppUnit::TestFactoryRegistry &mathRegistry = CppUnit::TestFactoryRegistry::getRegistry( "Math" ); + * CppUnit::TestSuite *mathSuite = mathRegistry.makeTest(); + * \endcode + * + * Example 3: creating a test suite hierarchy composed of unnamed registration and + * named registration: + * - All Tests + * - tests registered with CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( ..., "Graph" ) + * - tests registered with CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( ..., "Math" ) + * - tests registered with CPPUNIT_TEST_SUITE_REGISTRATION + * + * \code + * CppUnit::TestSuite *rootSuite = new CppUnit::TestSuite( "All tests" ); + * rootSuite->addTest( CppUnit::TestFactoryRegistry::getRegistry( "Graph" ).makeTest() ); + * rootSuite->addTest( CppUnit::TestFactoryRegistry::getRegistry( "Math" ).makeTest() ); + * CppUnit::TestFactoryRegistry::getRegistry().addTestToSuite( rootSuite ); + * \endcode + * + * The same result can be obtained with: + * \code + * CppUnit::TestFactoryRegistry ®istry = CppUnit::TestFactoryRegistry::getRegistry(); + * registry.addRegistry( "Graph" ); + * registry.addRegistry( "Math" ); + * CppUnit::TestSuite *suite = registry.makeTest(); + * \endcode + * + * Since a TestFactoryRegistry is a TestFactory, the named registries can be + * registered in the unnamed registry, creating the hierarchy links. + * + * \see TestSuiteFactory, AutoRegisterSuite + * \see CPPUNIT_TEST_SUITE_REGISTRATION, CPPUNIT_TEST_SUITE_NAMED_REGISTRATION + */ +class CPPUNIT_API TestFactoryRegistry : public TestFactory +{ +public: + /** Constructs the registry with the specified name. + * \param name Name of the registry. It is the name of TestSuite returned by + * makeTest(). + */ + TestFactoryRegistry( std::string name ); + + /// Destructor. + virtual ~TestFactoryRegistry(); + + /** Returns a new TestSuite that contains the registered test. + * \return A new TestSuite which contains all the test added using + * registerFactory(TestFactory *). + */ + virtual Test *makeTest(); + + /** Returns a named registry. + * + * If the \a name is left to its default value, then the registry that is returned is + * the one used by CPPUNIT_TEST_SUITE_REGISTRATION(): the 'top' level registry. + * + * \param name Name of the registry to return. + * \return Registry. If the registry does not exist, it is created with the + * specified name. + */ + static TestFactoryRegistry &getRegistry( const std::string &name = "All Tests" ); + + /** Adds the registered tests to the specified suite. + * \param suite Suite the tests are added to. + */ + void addTestToSuite( TestSuite *suite ); + + /** Adds the specified TestFactory to the registry. + * + * \param factory Factory to register. + */ + void registerFactory( TestFactory *factory ); + + /*! Removes the specified TestFactory from the registry. + * + * The specified factory is not destroyed. + * \param factory Factory to remove from the registry. + * \todo Address case when trying to remove a TestRegistryFactory. + */ + void unregisterFactory( TestFactory *factory ); + + /*! Adds a registry to the registry. + * + * Convenience method to help create test hierarchy. See TestFactoryRegistry detail + * for examples of use. Calling this method is equivalent to: + * \code + * this->registerFactory( TestFactoryRegistry::getRegistry( name ) ); + * \endcode + * + * \param name Name of the registry to add. + */ + void addRegistry( const std::string &name ); + + /*! Tests if the registry is valid. + * + * This method should be used when unregistering test factory on static variable + * destruction to ensure that the registry has not been already destroyed (in + * that case there is no need to unregister the test factory). + * + * You should not concern yourself with this method unless you are writing a class + * like AutoRegisterSuite. + * + * \return \c true if the specified registry has not been destroyed, + * otherwise returns \c false. + * \see AutoRegisterSuite. + */ + static bool isValid(); + + /** Adds the specified TestFactory with a specific name (DEPRECATED). + * \param name Name associated to the factory. + * \param factory Factory to register. + * \deprecated Use registerFactory( TestFactory *) instead. + */ + void registerFactory( const std::string &name, + TestFactory *factory ); + +private: + TestFactoryRegistry( const TestFactoryRegistry © ); + void operator =( const TestFactoryRegistry © ); + +private: + typedef CppUnitSet > Factories; + Factories m_factories; + + std::string m_name; +}; + + +CPPUNIT_NS_END + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( pop ) +#endif + + +#endif // CPPUNIT_EXTENSIONS_TESTFACTORYREGISTRY_H diff --git a/3rdParty/include/cppunit/extensions/TestFixtureFactory.h b/3rdParty/include/cppunit/extensions/TestFixtureFactory.h new file mode 100644 index 0000000000..45354c62be --- /dev/null +++ b/3rdParty/include/cppunit/extensions/TestFixtureFactory.h @@ -0,0 +1,50 @@ +#ifndef CPPUNIT_EXTENSIONS_TESTFIXTUREFACTORY_H +#define CPPUNIT_EXTENSIONS_TESTFIXTUREFACTORY_H + +#include + + +CPPUNIT_NS_BEGIN + + +class TestFixture; + +/*! \brief Abstract TestFixture factory (Implementation). + * + * Implementation detail. Use by HelperMacros to handle TestFixture hierarchy. + */ +class TestFixtureFactory +{ +public: + //! Creates a new TestFixture instance. + virtual TestFixture *makeFixture() =0; + + virtual ~TestFixtureFactory() {} +}; + + +/*! \brief Concret TestFixture factory (Implementation). + * + * Implementation detail. Use by HelperMacros to handle TestFixture hierarchy. + */ +template +class ConcretTestFixtureFactory : public CPPUNIT_NS::TestFixtureFactory +{ + /*! \brief Returns a new TestFixture instance. + * \return A new fixture instance. The fixture instance is returned by + * the TestFixtureFactory passed on construction. The actual type + * is that of the fixture on which the static method suite() + * was called. + */ + TestFixture *makeFixture() + { + return new TestFixtureType(); + } +}; + + +CPPUNIT_NS_END + + +#endif // CPPUNIT_EXTENSIONS_TESTFIXTUREFACTORY_H + diff --git a/3rdParty/include/cppunit/extensions/TestNamer.h b/3rdParty/include/cppunit/extensions/TestNamer.h new file mode 100644 index 0000000000..5a6471c0c3 --- /dev/null +++ b/3rdParty/include/cppunit/extensions/TestNamer.h @@ -0,0 +1,89 @@ +#ifndef CPPUNIT_EXTENSIONS_TESTNAMER_H +#define CPPUNIT_EXTENSIONS_TESTNAMER_H + +#include +#include + +#if CPPUNIT_HAVE_RTTI +# include +#endif + + + +/*! \def CPPUNIT_TESTNAMER_DECL( variableName, FixtureType ) + * \brief Declares a TestNamer. + * + * Declares a TestNamer for the specified type, using RTTI if enabled, otherwise + * using macro string expansion. + * + * RTTI is used if CPPUNIT_USE_TYPEINFO_NAME is defined and not null. + * + * \code + * void someMethod() + * { + * CPPUNIT_TESTNAMER_DECL( namer, AFixtureType ); + * std::string fixtureName = namer.getFixtureName(); + * ... + * \endcode + * + * \relates TestNamer + * \see TestNamer + */ +#if CPPUNIT_USE_TYPEINFO_NAME +# define CPPUNIT_TESTNAMER_DECL( variableName, FixtureType ) \ + CPPUNIT_NS::TestNamer variableName( typeid(FixtureType) ) +#else +# define CPPUNIT_TESTNAMER_DECL( variableName, FixtureType ) \ + CPPUNIT_NS::TestNamer variableName( std::string(#FixtureType) ) +#endif + + + +CPPUNIT_NS_BEGIN + + +/*! \brief Names a test or a fixture suite. + * + * TestNamer is usually instantiated using CPPUNIT_TESTNAMER_DECL. + * + */ +class CPPUNIT_API TestNamer +{ +public: +#if CPPUNIT_HAVE_RTTI + /*! \brief Constructs a namer using the fixture's type-info. + * \param typeInfo Type-info of the fixture type. Use to name the fixture suite. + */ + TestNamer( const std::type_info &typeInfo ); +#endif + + /*! \brief Constructs a namer using the specified fixture name. + * \param fixtureName Name of the fixture suite. Usually extracted using a macro. + */ + TestNamer( const std::string &fixtureName ); + + virtual ~TestNamer(); + + /*! \brief Returns the name of the fixture. + * \return Name of the fixture. + */ + virtual std::string getFixtureName() const; + + /*! \brief Returns the name of the test for the specified method. + * \param testMethodName Name of the method that implements a test. + * \return A string that is the concatenation of the test fixture name + * (returned by getFixtureName()) and\a testMethodName, + * separated using '::'. This provides a fairly unique name for a given + * test. + */ + virtual std::string getTestNameFor( const std::string &testMethodName ) const; + +protected: + std::string m_fixtureName; +}; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_EXTENSIONS_TESTNAMER_H + diff --git a/3rdParty/include/cppunit/extensions/TestSetUp.h b/3rdParty/include/cppunit/extensions/TestSetUp.h new file mode 100644 index 0000000000..f2128ecd74 --- /dev/null +++ b/3rdParty/include/cppunit/extensions/TestSetUp.h @@ -0,0 +1,34 @@ +#ifndef CPPUNIT_EXTENSIONS_TESTSETUP_H +#define CPPUNIT_EXTENSIONS_TESTSETUP_H + +#include + +CPPUNIT_NS_BEGIN + + +class Test; +class TestResult; + +/*! \brief Decorates a test by providing a specific setUp() and tearDown(). + */ +class CPPUNIT_API TestSetUp : public TestDecorator +{ +public: + TestSetUp( Test *test ); + + void run( TestResult *result ); + +protected: + virtual void setUp(); + virtual void tearDown(); + +private: + TestSetUp( const TestSetUp & ); + void operator =( const TestSetUp & ); +}; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_EXTENSIONS_TESTSETUP_H + diff --git a/3rdParty/include/cppunit/extensions/TestSuiteBuilderContext.h b/3rdParty/include/cppunit/extensions/TestSuiteBuilderContext.h new file mode 100644 index 0000000000..db26926cd4 --- /dev/null +++ b/3rdParty/include/cppunit/extensions/TestSuiteBuilderContext.h @@ -0,0 +1,131 @@ +#ifndef CPPUNIT_HELPER_TESTSUITEBUILDERCONTEXT_H +#define CPPUNIT_HELPER_TESTSUITEBUILDERCONTEXT_H + +#include +#include +#include + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( push ) +#pragma warning( disable: 4251 ) // X needs to have dll-interface to be used by clients of class Z +#endif + + +CPPUNIT_NS_BEGIN + +class TestSuite; +class TestFixture; +class TestFixtureFactory; +class TestNamer; + +/*! \brief Context used when creating test suite in HelperMacros. + * + * Base class for all context used when creating test suite. The + * actual context type during test suite creation is TestSuiteBuilderContext. + * + * \sa CPPUNIT_TEST_SUITE, CPPUNIT_TEST_SUITE_ADD_TEST, + * CPPUNIT_TEST_SUITE_ADD_CUSTOM_TESTS. + */ +class CPPUNIT_API TestSuiteBuilderContextBase +{ +public: + /*! \brief Constructs a new context. + * + * You should not use this. The context is created in + * CPPUNIT_TEST_SUITE(). + */ + TestSuiteBuilderContextBase( TestSuite &suite, + const TestNamer &namer, + TestFixtureFactory &factory ); + + virtual ~TestSuiteBuilderContextBase(); + + /*! \brief Adds a test to the fixture suite. + * + * \param test Test to add to the fixture suite. Must not be \c NULL. + */ + void addTest( Test *test ); + + /*! \brief Returns the fixture name. + * \return Fixture name. It is the name used to name the fixture + * suite. + */ + std::string getFixtureName() const; + + /*! \brief Returns the name of the test for the specified method. + * + * \param testMethodName Name of the method that implements a test. + * \return A string that is the concatenation of the test fixture name + * (returned by getFixtureName()) and\a testMethodName, + * separated using '::'. This provides a fairly unique name for a given + * test. + */ + std::string getTestNameFor( const std::string &testMethodName ) const; + + /*! \brief Adds property pair. + * \param key PropertyKey string to add. + * \param value PropertyValue string to add. + */ + void addProperty( const std::string &key, + const std::string &value ); + + /*! \brief Returns property value assigned to param key. + * \param key PropertyKey string. + */ + const std::string getStringProperty( const std::string &key ) const; + +protected: + TestFixture *makeTestFixture() const; + + // Notes: we use a vector here instead of a map to work-around the + // shared std::map in dll bug in VC6. + // See http://www.dinkumware.com/vc_fixes.html for detail. + typedef std::pair Property; + typedef CppUnitVector Properties; + + TestSuite &m_suite; + const TestNamer &m_namer; + TestFixtureFactory &m_factory; + +private: + Properties m_properties; +}; + + +/*! \brief Type-sage context used when creating test suite in HelperMacros. + * + * \sa TestSuiteBuilderContextBase. + */ +template +class TestSuiteBuilderContext : public TestSuiteBuilderContextBase +{ +public: + typedef Fixture FixtureType; + + TestSuiteBuilderContext( TestSuiteBuilderContextBase &contextBase ) + : TestSuiteBuilderContextBase( contextBase ) + { + } + + /*! \brief Returns a new TestFixture instance. + * \return A new fixture instance. The fixture instance is returned by + * the TestFixtureFactory passed on construction. The actual type + * is that of the fixture on which the static method suite() + * was called. + */ + FixtureType *makeFixture() const + { + return CPPUNIT_STATIC_CAST( FixtureType *, + TestSuiteBuilderContextBase::makeTestFixture() ); + } +}; + + +CPPUNIT_NS_END + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( pop ) +#endif + +#endif // CPPUNIT_HELPER_TESTSUITEBUILDERCONTEXT_H + diff --git a/3rdParty/include/cppunit/extensions/TestSuiteFactory.h b/3rdParty/include/cppunit/extensions/TestSuiteFactory.h new file mode 100644 index 0000000000..260b483151 --- /dev/null +++ b/3rdParty/include/cppunit/extensions/TestSuiteFactory.h @@ -0,0 +1,27 @@ +#ifndef CPPUNIT_EXTENSIONS_TESTSUITEFACTORY_H +#define CPPUNIT_EXTENSIONS_TESTSUITEFACTORY_H + +#include + +CPPUNIT_NS_BEGIN + + + class Test; + + /*! \brief TestFactory for TestFixture that implements a static suite() method. + * \see AutoRegisterSuite. + */ + template + class TestSuiteFactory : public TestFactory + { + public: + virtual Test *makeTest() + { + return TestCaseType::suite(); + } + }; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_EXTENSIONS_TESTSUITEFACTORY_H diff --git a/3rdParty/include/cppunit/extensions/TypeInfoHelper.h b/3rdParty/include/cppunit/extensions/TypeInfoHelper.h new file mode 100644 index 0000000000..c0ecdbc6e4 --- /dev/null +++ b/3rdParty/include/cppunit/extensions/TypeInfoHelper.h @@ -0,0 +1,33 @@ +#ifndef CPPUNIT_TYPEINFOHELPER_H +#define CPPUNIT_TYPEINFOHELPER_H + +#include + +#if CPPUNIT_HAVE_RTTI + +#include +#include + +CPPUNIT_NS_BEGIN + + + /**! \brief Helper to use type_info. + */ + class CPPUNIT_API TypeInfoHelper + { + public: + /*! \brief Get the class name of the specified type_info. + * \param info Info which the class name is extracted from. + * \return The string returned by type_info::name() without + * the "class" prefix. If the name is not prefixed + * by "class", it is returned as this. + */ + static std::string getClassName( const std::type_info &info ); + }; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_HAVE_RTTI + +#endif // CPPUNIT_TYPEINFOHELPER_H diff --git a/3rdParty/include/cppunit/plugin/DynamicLibraryManager.h b/3rdParty/include/cppunit/plugin/DynamicLibraryManager.h new file mode 100644 index 0000000000..d70ccde462 --- /dev/null +++ b/3rdParty/include/cppunit/plugin/DynamicLibraryManager.h @@ -0,0 +1,121 @@ +#ifndef CPPUNIT_PLUGIN_DYNAMICLIBRARYMANAGER_H +#define CPPUNIT_PLUGIN_DYNAMICLIBRARYMANAGER_H + +#include +#include + +#if !defined(CPPUNIT_NO_TESTPLUGIN) + +CPPUNIT_NS_BEGIN + + +/*! \brief Manages dynamic libraries. + * + * The Dynamic Library Manager provides a platform independent way to work with + * dynamic library. It load a specific dynamic library, and can returns specific + * symbol exported by the dynamic library. + * + * If an error occurs, a DynamicLibraryManagerException is thrown. + * + * \internal Implementation of the OS independent methods is in + * DynamicLibraryManager.cpp. + * + * \internal Porting to a new platform: + * - Adds platform detection in config/SelectDllLoader.h. Should define a specific + * macro for that platform of the form: CPPUNIT_HAVE_XYZ_DLL_LOADER, where + * XYZ is the platform. + * - Makes a copy of UnixDynamicLibraryManager.cpp and named it after the platform. + * - Updated the 'guard' in your file (CPPUNIT_HAVE_XYZ_DLL_LOADER) so that it is + * only processed if the matching platform has been detected. + * - Change the implementation of methods doLoadLibrary(), doReleaseLibrary(), + * doFindSymbol() in your copy. Those methods usually maps directly to OS calls. + * - Adds the file to the project. + */ +class DynamicLibraryManager +{ +public: + typedef void *Symbol; + typedef void *LibraryHandle; + + /*! \brief Loads the specified library. + * \param libraryFileName Name of the library to load. + * \exception DynamicLibraryManagerException if a failure occurs while loading + * the library (fail to found or load the library). + */ + DynamicLibraryManager( const std::string &libraryFileName ); + + /// Releases the loaded library.. + ~DynamicLibraryManager(); + + /*! \brief Returns a pointer on the specified symbol exported by the library. + * \param symbol Name of the symbol exported by the library. + * \return Pointer on the symbol. Should be casted to the actual type. Never \c NULL. + * \exception DynamicLibraryManagerException if the symbol is not found. + */ + Symbol findSymbol( const std::string &symbol ); + +private: + /*! Loads the specified library. + * \param libraryName Name of the library to load. + * \exception DynamicLibraryManagerException if a failure occurs while loading + * the library (fail to found or load the library). + */ + void loadLibrary( const std::string &libraryName ); + + /*! Releases the loaded library. + * + * \warning Must NOT throw any exceptions (called from destructor). + */ + void releaseLibrary(); + + /*! Loads the specified library. + * + * May throw any exceptions (indicates failure). + * \param libraryName Name of the library to load. + * \return Handle of the loaded library. \c NULL indicates failure. + */ + LibraryHandle doLoadLibrary( const std::string &libraryName ); + + /*! Releases the loaded library. + * + * The handle of the library to free is in \c m_libraryHandle. It is never + * \c NULL. + * \warning Must NOT throw any exceptions (called from destructor). + */ + void doReleaseLibrary(); + + /*! Returns a pointer on the specified symbol exported by the library. + * + * May throw any exceptions (indicates failure). + * \param symbol Name of the symbol exported by the library. + * \return Pointer on the symbol. \c NULL indicates failure. + */ + Symbol doFindSymbol( const std::string &symbol ); + + /*! Returns detailed information about doLoadLibrary() failure. + * + * Called just after a failed call to doLoadLibrary() to get extra + * error information. + * + * \return Detailed information about the failure of the call to + * doLoadLibrary() that just failed. + */ + std::string getLastErrorDetail() const; + + /// Prevents the use of the copy constructor. + DynamicLibraryManager( const DynamicLibraryManager © ); + + /// Prevents the use of the copy operator. + void operator =( const DynamicLibraryManager © ); + +private: + LibraryHandle m_libraryHandle; + std::string m_libraryName; +}; + + +CPPUNIT_NS_END + +#endif // !defined(CPPUNIT_NO_TESTPLUGIN) + +#endif // CPPUNIT_PLUGIN_DYNAMICLIBRARYMANAGER_H diff --git a/3rdParty/include/cppunit/plugin/DynamicLibraryManagerException.h b/3rdParty/include/cppunit/plugin/DynamicLibraryManagerException.h new file mode 100644 index 0000000000..11ebbd9da6 --- /dev/null +++ b/3rdParty/include/cppunit/plugin/DynamicLibraryManagerException.h @@ -0,0 +1,53 @@ +#ifndef CPPUNIT_PLUGIN_DYNAMICLIBRARYMANAGEREXCEPTION_H +#define CPPUNIT_PLUGIN_DYNAMICLIBRARYMANAGEREXCEPTION_H + +#include + +#if !defined(CPPUNIT_NO_TESTPLUGIN) +#include +#include + + +CPPUNIT_NS_BEGIN + + +/*! \brief Exception thrown by DynamicLibraryManager when a failure occurs. + * + * Use getCause() to know what function caused the failure. + * + */ +class DynamicLibraryManagerException : public std::runtime_error +{ +public: + enum Cause + { + /// Failed to load the dynamic library + loadingFailed =0, + /// Symbol not found in the dynamic library + symbolNotFound + }; + + /// Failed to load the dynamic library or Symbol not found in the dynamic library. + DynamicLibraryManagerException( const std::string &libraryName, + const std::string &errorDetail, + Cause cause ); + + ~DynamicLibraryManagerException() throw() + { + } + + Cause getCause() const; + + const char *what() const throw(); + +private: + std::string m_message; + Cause m_cause; +}; + + +CPPUNIT_NS_END + +#endif // !defined(CPPUNIT_NO_TESTPLUGIN) + +#endif // CPPUNIT_PLUGIN_DYNAMICLIBRARYMANAGEREXCEPTION_H diff --git a/3rdParty/include/cppunit/plugin/PlugInManager.h b/3rdParty/include/cppunit/plugin/PlugInManager.h new file mode 100644 index 0000000000..6ecedc89b5 --- /dev/null +++ b/3rdParty/include/cppunit/plugin/PlugInManager.h @@ -0,0 +1,113 @@ +#ifndef CPPUNIT_PLUGIN_PLUGINMANAGER_H +#define CPPUNIT_PLUGIN_PLUGINMANAGER_H + +#include + +#if !defined(CPPUNIT_NO_TESTPLUGIN) + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( push ) +#pragma warning( disable: 4251 ) // X needs to have dll-interface to be used by clients of class Z +#endif + +#include +struct CppUnitTestPlugIn; + +CPPUNIT_NS_BEGIN + + +class DynamicLibraryManager; +class TestResult; +class XmlOutputter; + + +/*! \brief Manges TestPlugIn. + */ +class CPPUNIT_API PlugInManager +{ +public: + /*! Constructs a PlugInManager object. + */ + PlugInManager(); + + /// Destructor. + virtual ~PlugInManager(); + + /*! \brief Loads the specified plug-in. + * + * After being loaded, the CppUnitTestPlugIn::initialize() is called. + * + * \param libraryFileName Name of the file that contains the TestPlugIn. + * \param parameters List of string passed to the plug-in. + * \return Pointer on the DynamicLibraryManager associated to the library. + * Valid until the library is unloaded. Never \c NULL. + * \exception DynamicLibraryManagerException is thrown if an error occurs during loading. + */ + void load( const std::string &libraryFileName, + const PlugInParameters ¶meters = PlugInParameters() ); + + /*! \brief Unloads the specified plug-in. + * \param libraryFileName Name of the file that contains the TestPlugIn passed + * to a previous call to load(). + */ + void unload( const std::string &libraryFileName ); + + /*! \brief Gives a chance to each loaded plug-in to register TestListener. + * + * For each plug-in, call CppUnitTestPlugIn::addListener(). + */ + void addListener( TestResult *eventManager ); + + /*! \brief Gives a chance to each loaded plug-in to unregister TestListener. + * For each plug-in, call CppUnitTestPlugIn::removeListener(). + */ + void removeListener( TestResult *eventManager ); + + /*! \brief Provides a way for the plug-in to register some XmlOutputterHook. + */ + void addXmlOutputterHooks( XmlOutputter *outputter ); + + /*! \brief Called when the XmlOutputter is destroyed. + * + * Can be used to free some resources allocated by addXmlOutputterHooks(). + */ + void removeXmlOutputterHooks(); + +protected: + /*! \brief (INTERNAL) Information about a specific plug-in. + */ + struct PlugInInfo + { + std::string m_fileName; + DynamicLibraryManager *m_manager; + CppUnitTestPlugIn *m_interface; + }; + + /*! Unloads the specified plug-in. + * \param plugIn Information about the plug-in. + */ + void unload( PlugInInfo &plugIn ); + +private: + /// Prevents the use of the copy constructor. + PlugInManager( const PlugInManager © ); + + /// Prevents the use of the copy operator. + void operator =( const PlugInManager © ); + +private: + typedef CppUnitDeque PlugIns; + PlugIns m_plugIns; +}; + + +CPPUNIT_NS_END + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( pop ) +#endif + +#endif // !defined(CPPUNIT_NO_TESTPLUGIN) + + +#endif // CPPUNIT_PLUGIN_PLUGINMANAGER_H diff --git a/3rdParty/include/cppunit/plugin/PlugInParameters.h b/3rdParty/include/cppunit/plugin/PlugInParameters.h new file mode 100644 index 0000000000..c67d0f1d24 --- /dev/null +++ b/3rdParty/include/cppunit/plugin/PlugInParameters.h @@ -0,0 +1,36 @@ +#ifndef CPPUNIT_PLUGIN_PARAMETERS +#define CPPUNIT_PLUGIN_PARAMETERS + +#include + +#if !defined(CPPUNIT_NO_TESTPLUGIN) + +#include +#include + +CPPUNIT_NS_BEGIN + +/*! \brief Test plug-ins parameters. + */ +class CPPUNIT_API PlugInParameters +{ +public: + /// Constructs plug-in parameters from the specified command-line. + PlugInParameters( const std::string &commandLine = "" ); + + virtual ~PlugInParameters(); + + /// Returns the command line that was passed on construction. + std::string getCommandLine() const; + +private: + std::string m_commandLine; +}; + + +CPPUNIT_NS_END + +#endif // !defined(CPPUNIT_NO_TESTPLUGIN) + + +#endif // CPPUNIT_PLUGIN_PARAMETERS diff --git a/3rdParty/include/cppunit/plugin/TestPlugIn.h b/3rdParty/include/cppunit/plugin/TestPlugIn.h new file mode 100644 index 0000000000..1c9b929a5f --- /dev/null +++ b/3rdParty/include/cppunit/plugin/TestPlugIn.h @@ -0,0 +1,200 @@ +#ifndef CPPUNIT_PLUGIN_TESTPLUGIN +#define CPPUNIT_PLUGIN_TESTPLUGIN + +#include + +#if !defined(CPPUNIT_NO_TESTPLUGIN) + +#include + +CPPUNIT_NS_BEGIN + + +class Test; +class TestFactoryRegistry; +class TestResult; +class XmlOutputter; + +CPPUNIT_NS_END + +/*! \file + */ + + +/*! \brief Test plug-in interface. + * \ingroup WritingTestPlugIn + * + * This class define the interface implemented by test plug-in. A pointer to that + * interface is returned by the function exported by the test plug-in. + * + * Plug-in are loaded/unloaded by PlugInManager. When a plug-in is loaded, + * initialize() is called. Before unloading the plug-in, the PlugInManager + * call uninitialize(). + * + * addListener() and removeListener() are called respectively before and after + * the test run. + * + * addXmlOutputterHooks() and removeXmlOutputterHooks() are called respectively + * before and after writing the XML output using a XmlOutputter. + * + * \see CPPUNIT_PLUGIN_IMPLEMENT, CPPUNIT_PLUGIN_EXPORTED_FUNCTION_IMPL + * \see CppUnit::TestPlugInDefaultImpl, CppUnit::XmlOutputter. + */ +struct CppUnitTestPlugIn +{ + /*! \brief Called just after loading the dynamic library. + * + * Override this method to add additional suite to the registry, though this + * is preferably done using the macros (CPPUNIT_TEST_SUITE_REGISTRATION...). + * If you are creating a custom listener to extends the plug-in runner, + * you can use this to configure the listener using the \a parameters. + * + * You could also use the parameters to specify some global parameter, such + * as test datas location, database name... + * + * N.B.: Parameters interface is not define yet, and the plug-in runner does + * not yet support plug-in parameter. + */ + virtual void initialize( CPPUNIT_NS::TestFactoryRegistry *registry, + const CPPUNIT_NS::PlugInParameters ¶meters ) =0; + + /*! \brief Gives a chance to the plug-in to register TestListener. + * + * Override this method to add a TestListener for the test run. This is useful + * if you are writing a custom TestListener, but also if you need to + * setUp some global resource: listen to TestListener::startTestRun(), + * and TestListener::endTestRun(). + */ + virtual void addListener( CPPUNIT_NS::TestResult *eventManager ) =0; + + /*! \brief Gives a chance to the plug-in to remove its registered TestListener. + * + * Override this method to remove a TestListener that has been added. + */ + virtual void removeListener( CPPUNIT_NS::TestResult *eventManager ) =0; + + /*! \brief Provides a way for the plug-in to register some XmlOutputterHook. + */ + virtual void addXmlOutputterHooks( CPPUNIT_NS::XmlOutputter *outputter ) =0; + + /*! \brief Called when the XmlOutputter is destroyed. + * + * Can be used to free some resources allocated by addXmlOutputterHooks(). + */ + virtual void removeXmlOutputterHooks() = 0; + + /*! \brief Called just before unloading the dynamic library. + * + * Override this method to unregister test factory added in initialize(). + * This is necessary to keep the TestFactoryRegistry 'clean'. When + * the plug-in is unloaded from memory, the TestFactoryRegistry will hold + * reference on test that are no longer available if they are not + * unregistered. + */ + virtual void uninitialize( CPPUNIT_NS::TestFactoryRegistry *registry ) =0; + + virtual ~CppUnitTestPlugIn() {} +}; + + + +/*! \brief Name of the function exported by a test plug-in. + * \ingroup WritingTestPlugIn + * + * The signature of the exported function is: + * \code + * CppUnitTestPlugIn *CPPUNIT_PLUGIN_EXPORTED_NAME(void); + * \endcode + */ +#define CPPUNIT_PLUGIN_EXPORTED_NAME cppunitTestPlugIn + +/*! \brief Type of the function exported by a plug-in. + * \ingroup WritingTestPlugIn + */ +typedef CppUnitTestPlugIn *(*TestPlugInSignature)(); + + +/*! \brief Implements the function exported by the test plug-in + * \ingroup WritingTestPlugIn + */ +#define CPPUNIT_PLUGIN_EXPORTED_FUNCTION_IMPL( TestPlugInInterfaceType ) \ + CPPUNIT_PLUGIN_EXPORT CppUnitTestPlugIn *CPPUNIT_PLUGIN_EXPORTED_NAME(void) \ + { \ + static TestPlugInInterfaceType plugIn; \ + return &plugIn; \ + } \ + typedef char __CppUnitPlugInExportFunctionDummyTypeDef // dummy typedef so it can end with ';' + + +// Note: This include should remain after definition of CppUnitTestPlugIn +#include + + +/*! \def CPPUNIT_PLUGIN_IMPLEMENT_MAIN() + * \brief Implements the 'main' function for the plug-in. + * + * This macros implements the main() function for dynamic library. + * For example, WIN32 requires a DllMain function, while some Unix + * requires a main() function. This macros takes care of the implementation. + */ + +// Win32 +#if defined(CPPUNIT_HAVE_WIN32_DLL_LOADER) +#if !defined(APIENTRY) +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#define NOUSER +#define NOKERNEL +#define NOSOUND +#define NOMINMAX +#define BLENDFUNCTION void // for mingw & gcc +#include +#endif +#define CPPUNIT_PLUGIN_IMPLEMENT_MAIN() \ + BOOL APIENTRY DllMain( HANDLE hModule, \ + DWORD ul_reason_for_call, \ + LPVOID lpReserved ) \ + { \ + return TRUE; \ + } \ + typedef char __CppUnitPlugInImplementMainDummyTypeDef + +// Unix +#elif defined(CPPUNIT_HAVE_UNIX_DLL_LOADER) || defined(CPPUNIT_HAVE_UNIX_SHL_LOADER) +#define CPPUNIT_PLUGIN_IMPLEMENT_MAIN() \ + int main( int argc, char *argv[] ) \ + { \ + return 0; \ + } \ + typedef char __CppUnitPlugInImplementMainDummyTypeDef + + +// Other +#else // other platforms don't require anything specifics +#endif + + + +/*! \brief Implements and exports the test plug-in interface. + * \ingroup WritingTestPlugIn + * + * This macro exports the test plug-in function using the subclass, + * and implements the 'main' function for the plug-in using + * CPPUNIT_PLUGIN_IMPLEMENT_MAIN(). + * + * When using this macro, CppUnit must be linked as a DLL (shared library). + * Otherwise, tests registered to the TestFactoryRegistry in the DLL will + * not be visible to the DllPlugInTester. + * + * \see CppUnitTestPlugIn + * \see CPPUNIT_PLUGIN_EXPORTED_FUNCTION_IMPL(), CPPUNIT_PLUGIN_IMPLEMENT_MAIN(). + */ +#define CPPUNIT_PLUGIN_IMPLEMENT() \ + CPPUNIT_PLUGIN_EXPORTED_FUNCTION_IMPL( CPPUNIT_NS::TestPlugInDefaultImpl ); \ + CPPUNIT_PLUGIN_IMPLEMENT_MAIN() + + +#endif // !defined(CPPUNIT_NO_TESTPLUGIN) + + +#endif // CPPUNIT_PLUGIN_TESTPLUGIN diff --git a/3rdParty/include/cppunit/plugin/TestPlugInDefaultImpl.h b/3rdParty/include/cppunit/plugin/TestPlugInDefaultImpl.h new file mode 100644 index 0000000000..fa4b8078f9 --- /dev/null +++ b/3rdParty/include/cppunit/plugin/TestPlugInDefaultImpl.h @@ -0,0 +1,52 @@ +#ifndef CPPUNIT_PLUGIN_TESTPLUGINADAPTER +#define CPPUNIT_PLUGIN_TESTPLUGINADAPTER + +#include + +#if !defined(CPPUNIT_NO_TESTPLUGIN) + +#include + +CPPUNIT_NS_BEGIN + + +class TestSuite; + + +/*! \brief Default implementation of test plug-in interface. + * \ingroup WritingTestPlugIn + * + * Override getSuiteName() to specify the suite name. Default is "All Tests". + * + * CppUnitTestPlugIn::getTestSuite() returns a suite that contains + * all the test registered to the default test factory registry + * ( TestFactoryRegistry::getRegistry() ). + * + */ +class CPPUNIT_API TestPlugInDefaultImpl : public CppUnitTestPlugIn +{ +public: + TestPlugInDefaultImpl(); + + virtual ~TestPlugInDefaultImpl(); + + void initialize( TestFactoryRegistry *registry, + const PlugInParameters ¶meters ); + + void addListener( TestResult *eventManager ); + + void removeListener( TestResult *eventManager ); + + void addXmlOutputterHooks( XmlOutputter *outputter ); + + void removeXmlOutputterHooks(); + + void uninitialize( TestFactoryRegistry *registry ); +}; + + +CPPUNIT_NS_END + +#endif // !defined(CPPUNIT_NO_TESTPLUGIN) + +#endif // CPPUNIT_PLUGIN_TESTPLUGINADAPTER diff --git a/3rdParty/include/cppunit/portability/CppUnitDeque.h b/3rdParty/include/cppunit/portability/CppUnitDeque.h new file mode 100644 index 0000000000..bbab21f56c --- /dev/null +++ b/3rdParty/include/cppunit/portability/CppUnitDeque.h @@ -0,0 +1,25 @@ +#ifndef CPPUNIT_PORTABILITY_CPPUNITDEQUE_H +#define CPPUNIT_PORTABILITY_CPPUNITDEQUE_H + +// The technic used is similar to the wrapper of STLPort. + +#include +#include + + +#if CPPUNIT_STD_NEED_ALLOCATOR + +template +class CppUnitDeque : public std::deque +{ +public: +}; + +#else // CPPUNIT_STD_NEED_ALLOCATOR + +#define CppUnitDeque std::deque + +#endif + +#endif // CPPUNIT_PORTABILITY_CPPUNITDEQUE_H + diff --git a/3rdParty/include/cppunit/portability/CppUnitMap.h b/3rdParty/include/cppunit/portability/CppUnitMap.h new file mode 100644 index 0000000000..0cdc723a20 --- /dev/null +++ b/3rdParty/include/cppunit/portability/CppUnitMap.h @@ -0,0 +1,29 @@ +#ifndef CPPUNIT_PORTABILITY_CPPUNITMAP_H +#define CPPUNIT_PORTABILITY_CPPUNITMAP_H + +// The technic used is similar to the wrapper of STLPort. + +#include +#include +#include + + +#if CPPUNIT_STD_NEED_ALLOCATOR + +template +class CppUnitMap : public std::map + ,CPPUNIT_STD_ALLOCATOR> +{ +public: +}; + +#else // CPPUNIT_STD_NEED_ALLOCATOR + +#define CppUnitMap std::map + +#endif + +#endif // CPPUNIT_PORTABILITY_CPPUNITMAP_H + diff --git a/3rdParty/include/cppunit/portability/CppUnitSet.h b/3rdParty/include/cppunit/portability/CppUnitSet.h new file mode 100644 index 0000000000..18b8662ebf --- /dev/null +++ b/3rdParty/include/cppunit/portability/CppUnitSet.h @@ -0,0 +1,28 @@ +#ifndef CPPUNIT_PORTABILITY_CPPUNITSET_H +#define CPPUNIT_PORTABILITY_CPPUNITSET_H + +// The technic used is similar to the wrapper of STLPort. + +#include +#include +#include + + +#if CPPUNIT_STD_NEED_ALLOCATOR + +template +class CppUnitSet : public std::set + ,CPPUNIT_STD_ALLOCATOR> +{ +public: +}; + +#else // CPPUNIT_STD_NEED_ALLOCATOR + +#define CppUnitSet std::set + +#endif + +#endif // CPPUNIT_PORTABILITY_CPPUNITSET_H + diff --git a/3rdParty/include/cppunit/portability/CppUnitStack.h b/3rdParty/include/cppunit/portability/CppUnitStack.h new file mode 100644 index 0000000000..bc7785b037 --- /dev/null +++ b/3rdParty/include/cppunit/portability/CppUnitStack.h @@ -0,0 +1,26 @@ +#ifndef CPPUNIT_PORTABILITY_CPPUNITSTACK_H +#define CPPUNIT_PORTABILITY_CPPUNITSTACK_H + +// The technic used is similar to the wrapper of STLPort. + +#include +#include +#include + + +#if CPPUNIT_STD_NEED_ALLOCATOR + +template +class CppUnitStack : public std::stack > +{ +public: +}; + +#else // CPPUNIT_STD_NEED_ALLOCATOR + +#define CppUnitStack std::stack + +#endif + +#endif // CPPUNIT_PORTABILITY_CPPUNITSTACK_H \ No newline at end of file diff --git a/3rdParty/include/cppunit/portability/CppUnitVector.h b/3rdParty/include/cppunit/portability/CppUnitVector.h new file mode 100644 index 0000000000..6666a63b0d --- /dev/null +++ b/3rdParty/include/cppunit/portability/CppUnitVector.h @@ -0,0 +1,25 @@ +#ifndef CPPUNIT_PORTABILITY_CPPUNITVECTOR_H +#define CPPUNIT_PORTABILITY_CPPUNITVECTOR_H + +// The technic used is similar to the wrapper of STLPort. + +#include +#include + + +#if CPPUNIT_STD_NEED_ALLOCATOR + +template +class CppUnitVector : public std::vector +{ +public: +}; + +#else // CPPUNIT_STD_NEED_ALLOCATOR + +#define CppUnitVector std::vector + +#endif + +#endif // CPPUNIT_PORTABILITY_CPPUNITVECTOR_H + diff --git a/3rdParty/include/cppunit/portability/FloatingPoint.h b/3rdParty/include/cppunit/portability/FloatingPoint.h new file mode 100644 index 0000000000..e8c91b3f5c --- /dev/null +++ b/3rdParty/include/cppunit/portability/FloatingPoint.h @@ -0,0 +1,54 @@ +#ifndef CPPUNIT_PORTABILITY_FLOATINGPOINT_H_INCLUDED +#define CPPUNIT_PORTABILITY_FLOATINGPOINT_H_INCLUDED + +#include +#include + +CPPUNIT_NS_BEGIN + +/// \brief Tests if a floating-point is a NaN. +// According to IEEE-754 floating point standard, +// (see e.g. page 8 of +// http://www.cs.berkeley.edu/~wkahan/ieee754status/ieee754.ps) +// all comparisons with NaN are false except "x != x", which is true. +// +// At least Microsoft Visual Studio 6 is known not to implement this test correctly. +// It emits the following code to test equality: +// fcomp qword ptr [nan] +// fnstsw ax // copie fp (floating-point) status register to ax +// test ah,40h // test bit 14 of ax (0x4000) => C3 of fp status register +// According to the following documentation on the x86 floating point status register, +// the C2 bit should be tested to test for NaN value. +// http://webster.cs.ucr.edu/AoA/Windows/HTML/RealArithmetic.html#1000117 +// In Microsoft Visual Studio 2003 & 2005, the test is implemented with: +// test ah,44h // Visual Studio 2005 test both C2 & C3... +// +// To work around this, a NaN is assumed to be detected if no strict ordering is found. +inline bool floatingPointIsUnordered( double x ) +{ + // x != x will detect a NaN on conformant platform + // (2.0 < x && x < 1.0) will detect a NaN on non conformant platform: + // => no ordering can be found for x. + return (x != x) || (2.0 < x && x < 1.0); +} + + +/// \brief Tests if a floating-point is finite. +/// @return \c true if x is neither a NaN, nor +inf, nor -inf, \c false otherwise. +inline int floatingPointIsFinite( double x ) +{ +#if defined(CPPUNIT_HAVE_ISFINITE) + return isfinite( x ); +#elif defined(CPPUNIT_HAVE_FINITE) + return finite( x ); +#elif defined(CPPUNIT_HAVE__FINITE) + return _finite(x); +#else + double testInf = x * 0.0; // Produce 0.0 if x is finite, a NaN otherwise. + return testInf == 0.0 && !floatingPointIsUnordered(testInf); +#endif +} + +CPPUNIT_NS_END + +#endif // CPPUNIT_PORTABILITY_FLOATINGPOINT_H_INCLUDED diff --git a/3rdParty/include/cppunit/portability/Stream.h b/3rdParty/include/cppunit/portability/Stream.h new file mode 100644 index 0000000000..e9beb8c1b2 --- /dev/null +++ b/3rdParty/include/cppunit/portability/Stream.h @@ -0,0 +1,347 @@ +#ifndef CPPUNIT_PORTABILITY_STREAM_H_INCLUDED +#define CPPUNIT_PORTABILITY_STREAM_H_INCLUDED + +// This module define: +// Type CppUT::Stream (either std::stream or a custom type) +// Type CppUT::OStringStream (eitjer std::ostringstream, older alternate or a custom type) +// Functions stdCOut() & stdCErr() which returns a reference on cout & cerr stream (or our +// custom stream). + +#include + + +#if defined( CPPUNIT_NO_STREAM ) + +#include +#include +#include + +CPPUNIT_NS_BEGIN + +class StreamBuffer +{ +public: + virtual ~StreamBuffer() {} + + virtual void write( const char *text, unsigned int length ) = 0; + + virtual void flush() {} +}; + + +class StringStreamBuffer : public StreamBuffer +{ +public: + std::string str() const + { + return str_; + } + +public: // overridden from StreamBuffer + void write( const char *text, unsigned int length ) + { + str_.append( text, length ); + } + +private: + std::string str_; +}; + + +class FileStreamBuffer : public StreamBuffer +{ +public: + FileStreamBuffer( FILE *file ) + : file_( file ) + { + } + + FILE *file() const + { + return file_; + } + +public: // overridden from StreamBuffer + void write( const char *text, unsigned int length ) + { + if ( file_ ) + fwrite( text, sizeof(char), length, file_ ); + } + + void flush() + { + if ( file_ ) + fflush( file_ ); + } + +private: + FILE *file_; +}; + + +class OStream +{ +public: + OStream() + : buffer_( 0 ) + { + } + + OStream( StreamBuffer *buffer ) + : buffer_( buffer ) + { + } + + virtual ~OStream() + { + flush(); + } + + OStream &flush() + { + if ( buffer_ ) + buffer_->flush(); + return *this; + } + + void setBuffer( StreamBuffer *buffer ) + { + buffer_ = buffer; + } + + OStream &write( const char *text, unsigned int length ) + { + if ( buffer_ ) + buffer_->write( text, length ); + return *this; + } + + OStream &write( const char *text ) + { + return write( text, strlen(text) ); + } + + OStream &operator <<( bool v ) + { + const char *out = v ? "true" : "false"; + return write( out ); + } + + OStream &operator <<( short v ) + { + char buffer[64]; + sprintf( buffer, "%hd", v ); + return write( buffer ); + } + + OStream &operator <<( unsigned short v ) + { + char buffer[64]; + sprintf( buffer, "%hu", v ); + return write( buffer ); + } + + OStream &operator <<( int v ) + { + char buffer[64]; + sprintf( buffer, "%d", v ); + return write( buffer ); + } + + OStream &operator <<( unsigned int v ) + { + char buffer[64]; + sprintf( buffer, "%u", v ); + return write( buffer ); + } + + OStream &operator <<( long v ) + { + char buffer[64]; + sprintf( buffer, "%ld", v ); + return write( buffer ); + } + + OStream &operator <<( unsigned long v ) + { + char buffer[64]; + sprintf( buffer, "%lu", v ); + return write( buffer ); + } + + OStream &operator <<( float v ) + { + char buffer[128]; + sprintf( buffer, "%.16g", double(v) ); + return write( buffer ); + } + + OStream &operator <<( double v ) + { + char buffer[128]; + sprintf( buffer, "%.16g", v ); + return write( buffer ); + } + + OStream &operator <<( long double v ) + { + char buffer[128]; + sprintf( buffer, "%.16g", double(v) ); + return write( buffer ); + } + + OStream &operator <<( const void *v ) + { + char buffer[64]; + sprintf( buffer, "%p", v ); + return write( buffer ); + } + + OStream &operator <<( const char *v ) + { + return write( v ? v : "NULL" ); + } + + OStream &operator <<( char c ) + { + char buffer[16]; + sprintf( buffer, "%c", c ); + return write( buffer ); + } + + OStream &operator <<( const std::string &s ) + { + return write( s.c_str(), s.length() ); + } + +private: + StreamBuffer *buffer_; +}; + + +class OStringStream : public OStream +{ +public: + OStringStream() + : OStream( &buffer_ ) + { + } + + std::string str() const + { + return buffer_.str(); + } + +private: + StringStreamBuffer buffer_; +}; + + +class OFileStream : public OStream +{ +public: + OFileStream( FILE *file ) + : OStream( &buffer_ ) + , buffer_( file ) + , ownFile_( false ) + { + } + + OFileStream( const char *path ) + : OStream( &buffer_ ) + , buffer_( fopen( path, "wt" ) ) + , ownFile_( true ) + { + } + + virtual ~OFileStream() + { + if ( ownFile_ && buffer_.file() ) + fclose( buffer_.file() ); + } + +private: + FileStreamBuffer buffer_; + bool ownFile_; +}; + +inline OStream &stdCOut() +{ + static OFileStream stream( stdout ); + return stream; +} + +inline OStream &stdCErr() +{ + static OFileStream stream( stderr ); + return stream; +} + +CPPUNIT_NS_END + +#elif CPPUNIT_HAVE_SSTREAM // #if defined( CPPUNIT_NO_STREAM ) +# include +# include + + CPPUNIT_NS_BEGIN + typedef std::ostringstream OStringStream; // The standard C++ way + typedef std::ofstream OFileStream; + CPPUNIT_NS_END + + +#elif CPPUNIT_HAVE_CLASS_STRSTREAM +# include +# if CPPUNIT_HAVE_STRSTREAM +# include +# else // CPPUNIT_HAVE_STRSTREAM +# include +# endif // CPPUNIT_HAVE_CLASS_STRSTREAM + + CPPUNIT_NS_BEGIN + + class OStringStream : public std::ostrstream + { + public: + std::string str() + { +// (*this) << '\0'; +// std::string msg(std::ostrstream::str()); +// std::ostrstream::freeze(false); +// return msg; +// Alternative implementation that don't rely on freeze which is not +// available on some platforms: + return std::string( std::ostrstream::str(), pcount() ); + } + }; + + CPPUNIT_NS_END +#else // CPPUNIT_HAVE_CLASS_STRSTREAM +# error Cannot define CppUnit::OStringStream. +#endif // #if defined( CPPUNIT_NO_STREAM ) + + + +#if !defined( CPPUNIT_NO_STREAM ) + +#include + + CPPUNIT_NS_BEGIN + + typedef std::ostream OStream; + + inline OStream &stdCOut() + { + return std::cout; + } + + inline OStream &stdCErr() + { + return std::cerr; + } + + CPPUNIT_NS_END + +#endif // #if !defined( CPPUNIT_NO_STREAM ) + +#endif // CPPUNIT_PORTABILITY_STREAM_H_INCLUDED + diff --git a/3rdParty/include/cppunit/tools/Algorithm.h b/3rdParty/include/cppunit/tools/Algorithm.h new file mode 100644 index 0000000000..e5746a2bf5 --- /dev/null +++ b/3rdParty/include/cppunit/tools/Algorithm.h @@ -0,0 +1,23 @@ +#ifndef CPPUNIT_TOOLS_ALGORITHM_H_INCLUDED +#define CPPUNIT_TOOLS_ALGORITHM_H_INCLUDED + +#include + +CPPUNIT_NS_BEGIN + +template +void +removeFromSequence( SequenceType &sequence, + const ValueType &valueToRemove ) +{ + for ( unsigned int index =0; index < sequence.size(); ++index ) + { + if ( sequence[ index ] == valueToRemove ) + sequence.erase( sequence.begin() + index ); + } +} + +CPPUNIT_NS_END + + +#endif // CPPUNIT_TOOLS_ALGORITHM_H_INCLUDED diff --git a/3rdParty/include/cppunit/tools/StringTools.h b/3rdParty/include/cppunit/tools/StringTools.h new file mode 100644 index 0000000000..7a6b6d710d --- /dev/null +++ b/3rdParty/include/cppunit/tools/StringTools.h @@ -0,0 +1,34 @@ +#ifndef CPPUNIT_TOOLS_STRINGTOOLS_H +#define CPPUNIT_TOOLS_STRINGTOOLS_H + +#include +#include +#include + + +CPPUNIT_NS_BEGIN + + +/*! \brief Tool functions to manipulate string. + */ +struct StringTools +{ + + typedef CppUnitVector Strings; + + static std::string CPPUNIT_API toString( int value ); + + static std::string CPPUNIT_API toString( double value ); + + static Strings CPPUNIT_API split( const std::string &text, + char separator ); + + static std::string CPPUNIT_API wrap( const std::string &text, + int wrapColumn = CPPUNIT_WRAP_COLUMN ); + +}; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_TOOLS_STRINGTOOLS_H diff --git a/3rdParty/include/cppunit/tools/XmlDocument.h b/3rdParty/include/cppunit/tools/XmlDocument.h new file mode 100644 index 0000000000..4ee73257ad --- /dev/null +++ b/3rdParty/include/cppunit/tools/XmlDocument.h @@ -0,0 +1,86 @@ +#ifndef CPPUNIT_TOOLS_XMLDOCUMENT_H +#define CPPUNIT_TOOLS_XMLDOCUMENT_H + +#include + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( push ) +#pragma warning( disable: 4251 ) // X needs to have dll-interface to be used by clients of class Z +#endif + +#include + + +CPPUNIT_NS_BEGIN + + +class XmlElement; + + +/*! \brief A XML Document. + * + * A XmlDocument represents a XML file. It holds a pointer on the root XmlElement + * of the document. It also holds the encoding and style sheet used. + * + * By default, the XML document is stand-alone and tagged with enconding "ISO-8859-1". + */ +class CPPUNIT_API XmlDocument +{ +public: + /*! \brief Constructs a XmlDocument object. + * \param encoding Encoding used in the XML file (default is Latin-1, ISO-8859-1 ). + * \param styleSheet Name of the XSL style sheet file used. If empty then no + * style sheet will be specified in the output. + */ + XmlDocument( const std::string &encoding = "", + const std::string &styleSheet = "" ); + + /// Destructor. + virtual ~XmlDocument(); + + std::string encoding() const; + void setEncoding( const std::string &encoding = "" ); + + std::string styleSheet() const; + void setStyleSheet( const std::string &styleSheet = "" ); + + bool standalone() const; + + /*! \brief set the output document as standalone or not. + * + * For the output document, specify wether it's a standalone XML + * document, or not. + * + * \param standalone if true, the output will be specified as standalone. + * if false, it will be not. + */ + void setStandalone( bool standalone ); + + void setRootElement( XmlElement *rootElement ); + XmlElement &rootElement() const; + + std::string toString() const; + +private: + /// Prevents the use of the copy constructor. + XmlDocument( const XmlDocument © ); + + /// Prevents the use of the copy operator. + void operator =( const XmlDocument © ); + +protected: + std::string m_encoding; + std::string m_styleSheet; + XmlElement *m_rootElement; + bool m_standalone; +}; + + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( pop ) +#endif + + +CPPUNIT_NS_END + +#endif // CPPUNIT_TOOLS_XMLDOCUMENT_H diff --git a/3rdParty/include/cppunit/tools/XmlElement.h b/3rdParty/include/cppunit/tools/XmlElement.h new file mode 100644 index 0000000000..0b36bd2388 --- /dev/null +++ b/3rdParty/include/cppunit/tools/XmlElement.h @@ -0,0 +1,149 @@ +#ifndef CPPUNIT_TOOLS_XMLELEMENT_H +#define CPPUNIT_TOOLS_XMLELEMENT_H + +#include + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( push ) +#pragma warning( disable: 4251 ) // X needs to have dll-interface to be used by clients of class Z +#endif + +#include +#include + + +CPPUNIT_NS_BEGIN + + +class XmlElement; + +#if CPPUNIT_NEED_DLL_DECL +// template class CPPUNIT_API std::deque; +#endif + + +/*! \brief A XML Element. + * + * A XML element has: + * - a name, specified on construction, + * - a content, specified on construction (may be empty), + * - zero or more attributes, added with addAttribute(), + * - zero or more child elements, added with addElement(). + */ +class CPPUNIT_API XmlElement +{ +public: + /*! \brief Constructs an element with the specified name and string content. + * \param elementName Name of the element. Must not be empty. + * \param content Content of the element. + */ + XmlElement( std::string elementName, + std::string content ="" ); + + /*! \brief Constructs an element with the specified name and numeric content. + * \param elementName Name of the element. Must not be empty. + * \param numericContent Content of the element. + */ + XmlElement( std::string elementName, + int numericContent ); + + /*! \brief Destructs the element and its child elements. + */ + virtual ~XmlElement(); + + /*! \brief Returns the name of the element. + * \return Name of the element. + */ + std::string name() const; + + /*! \brief Returns the content of the element. + * \return Content of the element. + */ + std::string content() const; + + /*! \brief Sets the name of the element. + * \param name New name for the element. + */ + void setName( const std::string &name ); + + /*! \brief Sets the content of the element. + * \param content New content for the element. + */ + void setContent( const std::string &content ); + + /*! \overload void setContent( const std::string &content ) + */ + void setContent( int numericContent ); + + /*! \brief Adds an attribute with the specified string value. + * \param attributeName Name of the attribute. Must not be an empty. + * \param value Value of the attribute. + */ + void addAttribute( std::string attributeName, + std::string value ); + + /*! \brief Adds an attribute with the specified numeric value. + * \param attributeName Name of the attribute. Must not be empty. + * \param numericValue Numeric value of the attribute. + */ + void addAttribute( std::string attributeName, + int numericValue ); + + /*! \brief Adds a child element to the element. + * \param element Child element to add. Must not be \c NULL. + */ + void addElement( XmlElement *element ); + + /*! \brief Returns the number of child elements. + * \return Number of child elements (element added with addElement()). + */ + int elementCount() const; + + /*! \brief Returns the child element at the specified index. + * \param index Zero based index of the element to return. + * \returns Element at the specified index. Never \c NULL. + * \exception std::invalid_argument if \a index < 0 or index >= elementCount(). + */ + XmlElement *elementAt( int index ) const; + + /*! \brief Returns the first child element with the specified name. + * \param name Name of the child element to return. + * \return First child element found which is named \a name. + * \exception std::invalid_argument if there is no child element with the specified + * name. + */ + XmlElement *elementFor( const std::string &name ) const; + + /*! \brief Returns a XML string that represents the element. + * \param indent String of spaces representing the amount of 'indent'. + * \return XML string that represents the element, its attributes and its + * child elements. + */ + std::string toString( const std::string &indent = "" ) const; + +private: + typedef std::pair Attribute; + + std::string attributesAsString() const; + std::string escape( std::string value ) const; + +private: + std::string m_name; + std::string m_content; + + typedef CppUnitDeque Attributes; + Attributes m_attributes; + + typedef CppUnitDeque Elements; + Elements m_elements; +}; + + +CPPUNIT_NS_END + +#if CPPUNIT_NEED_DLL_DECL +#pragma warning( pop ) +#endif + + +#endif // CPPUNIT_TOOLS_XMLELEMENT_H diff --git a/3rdParty/include/cppunit/ui/mfc/MfcTestRunner.h b/3rdParty/include/cppunit/ui/mfc/MfcTestRunner.h new file mode 100644 index 0000000000..6a5f7b770f --- /dev/null +++ b/3rdParty/include/cppunit/ui/mfc/MfcTestRunner.h @@ -0,0 +1,76 @@ +#ifndef CPPUNITUI_MFC_MFCTESTRUNNER_H +#define CPPUNITUI_MFC_MFCTESTRUNNER_H + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#include +#include + +/* Refer to MSDN documentation to know how to write and use MFC extension DLL: + mk:@MSITStore:h:\DevStudio\MSDN\98VSa\1036\vcmfc.chm::/html/_mfcnotes_tn033.htm#_mfcnotes_how_to_write_an_mfc_extension_dll + + This can be found in the index with "mfc extension" + The basic: + Using: + - your application must use MFC DLL + - memory allocation is done using the same heap + - you must define the symbol _AFX_DLL + + Building: + - you must define the symbol _AFX_DLL and _AFX_EXT + - export class using AFX_EXT_CLASS + */ + +CPPUNIT_NS_BEGIN + + class Test; + class TestSuite; + + +/*! \brief MFC test runner. + * \ingroup ExecutingTest + * + * Use this to launch the MFC TestRunner. Usually called from you CWinApp subclass: + * + * \code + * #include + * #include + * + * void + * CHostAppApp::RunUnitTests() + * { + * CppUnit::MfcTestRunner runner; + * runner.addTest( CppUnit::TestFactoryRegistry::getRegistry().makeTest() ); + * + * runner.run(); + * } + * \endcode + * \see CppUnit::TextTestRunner, CppUnit::TestFactoryRegistry. + */ +class AFX_EXT_CLASS MfcTestRunner +{ +public: + MfcTestRunner(); + virtual ~MfcTestRunner(); + + void run(); + + void addTest( Test *test ); + + void addTests( const CppUnitVector &tests ); + +protected: + Test *getRootTest(); + + TestSuite *m_suite; + + typedef CppUnitVector Tests; + Tests m_tests; +}; + + +CPPUNIT_NS_END + +#endif // CPPUNITUI_MFC_MFCTESTRUNNER_H \ No newline at end of file diff --git a/3rdParty/include/cppunit/ui/mfc/TestRunner.h b/3rdParty/include/cppunit/ui/mfc/TestRunner.h new file mode 100644 index 0000000000..c4d6afface --- /dev/null +++ b/3rdParty/include/cppunit/ui/mfc/TestRunner.h @@ -0,0 +1,21 @@ +#ifndef CPPUNITUI_MFC_TESTRUNNER_H +#define CPPUNITUI_MFC_TESTRUNNER_H + +#include + +CPPUNIT_NS_BEGIN + +#if defined(CPPUNIT_HAVE_NAMESPACES) +namespace MfcUi +{ + /*! Mfc TestRunner (DEPRECATED). + * \deprecated Use CppUnit::MfcTestRunner instead. + */ + typedef CPPUNIT_NS::MfcTestRunner TestRunner; +} +#endif // defined(CPPUNIT_HAVE_NAMESPACES) + +CPPUNIT_NS_END + + +#endif // CPPUNITUI_MFC_TESTRUNNER_H diff --git a/3rdParty/include/cppunit/ui/qt/Config.h b/3rdParty/include/cppunit/ui/qt/Config.h new file mode 100644 index 0000000000..beaf9ad4e1 --- /dev/null +++ b/3rdParty/include/cppunit/ui/qt/Config.h @@ -0,0 +1,21 @@ +#ifndef CPPUNIT_QTUI_CONFIG_H +#define CPPUNIT_QTUI_CONFIG_H + +/*! Macro to export symbol to DLL with VC++. + * + * - QTTESTRUNNER_DLL_BUILD must be defined when building the DLL. + * - QTTESTRUNNER_DLL must be defined if linking against the DLL. + * - If none of the above are defined then you are building or linking against + * the static library. + */ + +#if defined( QTTESTRUNNER_DLL_BUILD ) +# define QTTESTRUNNER_API __declspec(dllexport) +#elif defined ( QTTESTRUNNER_DLL ) +# define QTTESTRUNNER_API __declspec(dllimport) +#else +# define QTTESTRUNNER_API +#endif + + +#endif // CPPUNIT_QTUI_CONFIG_H diff --git a/3rdParty/include/cppunit/ui/qt/QtTestRunner.h b/3rdParty/include/cppunit/ui/qt/QtTestRunner.h new file mode 100644 index 0000000000..4b6ab4e4ef --- /dev/null +++ b/3rdParty/include/cppunit/ui/qt/QtTestRunner.h @@ -0,0 +1,85 @@ +// ////////////////////////////////////////////////////////////////////////// +// Header file TestRunner.h for class TestRunner +// (c)Copyright 2000, Baptiste Lepilleur. +// Created: 2001/09/19 +// ////////////////////////////////////////////////////////////////////////// +#ifndef CPPUNIT_QTUI_QTTESTRUNNER_H +#define CPPUNIT_QTUI_QTTESTRUNNER_H + +#include +#include "Config.h" + +CPPUNIT_NS_BEGIN + + + class Test; + class TestSuite; + + +/*! + * \brief QT test runner. + * \ingroup ExecutingTest + * + * Here is an example of usage: + * \code + * #include + * #include + * + * [...] + * + * void + * QDepWindow::runTests() + * { + * CppUnit::QtUi::TestRunner runner; + * runner.addTest( CppUnit::TestFactoryRegistry::getRegistry().makeTest() ); + * runner.run( true ); + * } + * \endcode + * + */ +class QTTESTRUNNER_API QtTestRunner +{ +public: + /*! Constructs a TestRunner object. + */ + QtTestRunner(); + + /*! Destructor. + */ + virtual ~QtTestRunner(); + + void run( bool autoRun =false ); + + void addTest( Test *test ); + +private: + /// Prevents the use of the copy constructor. + QtTestRunner( const QtTestRunner © ); + + /// Prevents the use of the copy operator. + void operator =( const QtTestRunner © ); + + Test *getRootTest(); + +private: + typedef CppUnitVector Tests; + Tests *_tests; + + TestSuite *_suite; +}; + + +#if CPPUNIT_HAVE_NAMESPACES + namespace QtUi + { + /*! Qt TestRunner (DEPRECATED). + * \deprecated Use CppUnit::QtTestRunner instead. + */ + typedef CPPUNIT_NS::QtTestRunner TestRunner; + } +#endif + + +CPPUNIT_NS_END + +#endif // CPPUNIT_QTUI_QTTESTRUNNER_H diff --git a/3rdParty/include/cppunit/ui/qt/TestRunner.h b/3rdParty/include/cppunit/ui/qt/TestRunner.h new file mode 100644 index 0000000000..9c53e4ba60 --- /dev/null +++ b/3rdParty/include/cppunit/ui/qt/TestRunner.h @@ -0,0 +1,11 @@ +// ////////////////////////////////////////////////////////////////////////// +// Header file TestRunner.h for class TestRunner +// (c)Copyright 2000, Baptiste Lepilleur. +// Created: 2001/09/19 +// ////////////////////////////////////////////////////////////////////////// +#ifndef CPPUNIT_QTUI_TESTRUNNER_H +#define CPPUNIT_QTUI_TESTRUNNER_H + +#include + +#endif // CPPUNIT_QTUI_TESTRUNNER_H diff --git a/3rdParty/include/cppunit/ui/text/TestRunner.h b/3rdParty/include/cppunit/ui/text/TestRunner.h new file mode 100644 index 0000000000..023eb83489 --- /dev/null +++ b/3rdParty/include/cppunit/ui/text/TestRunner.h @@ -0,0 +1,24 @@ +#ifndef CPPUNIT_UI_TEXT_TESTRUNNER_H +#define CPPUNIT_UI_TEXT_TESTRUNNER_H + +#include + + +#if defined(CPPUNIT_HAVE_NAMESPACES) + +CPPUNIT_NS_BEGIN +namespace TextUi +{ + + /*! Text TestRunner (DEPRECATED). + * \deprecated Use TextTestRunner instead. + */ + typedef TextTestRunner TestRunner; + +} +CPPUNIT_NS_END + +#endif // defined(CPPUNIT_HAVE_NAMESPACES) + + +#endif // CPPUNIT_UI_TEXT_TESTRUNNER_H diff --git a/3rdParty/include/cppunit/ui/text/TextTestRunner.h b/3rdParty/include/cppunit/ui/text/TextTestRunner.h new file mode 100644 index 0000000000..86da4d4b30 --- /dev/null +++ b/3rdParty/include/cppunit/ui/text/TextTestRunner.h @@ -0,0 +1,97 @@ +#ifndef CPPUNIT_UI_TEXT_TEXTTESTRUNNER_H +#define CPPUNIT_UI_TEXT_TEXTTESTRUNNER_H + + +#include +#include +#include + +CPPUNIT_NS_BEGIN + + +class Outputter; +class Test; +class TestSuite; +class TextOutputter; +class TestResult; +class TestResultCollector; + + + +/*! + * \brief A text mode test runner. + * \ingroup WritingTestResult + * \ingroup ExecutingTest + * + * The test runner manage the life cycle of the added tests. + * + * The test runner can run only one of the added tests or all the tests. + * + * TestRunner prints out a trace as the tests are executed followed by a + * summary at the end. The trace and summary print are optional. + * + * Here is an example of use: + * + * \code + * CppUnit::TextTestRunner runner; + * runner.addTest( ExampleTestCase::suite() ); + * runner.run( "", true ); // Run all tests and wait + * \endcode + * + * The trace is printed using a TextTestProgressListener. The summary is printed + * using a TextOutputter. + * + * You can specify an alternate Outputter at construction + * or later with setOutputter(). + * + * After construction, you can register additional TestListener to eventManager(), + * for a custom progress trace, for example. + * + * \code + * CppUnit::TextTestRunner runner; + * runner.addTest( ExampleTestCase::suite() ); + * runner.setOutputter( CppUnit::CompilerOutputter::defaultOutputter( + * &runner.result(), + * std::cerr ) ); + * MyCustomProgressTestListener progress; + * runner.eventManager().addListener( &progress ); + * runner.run( "", true ); // Run all tests and wait + * \endcode + * + * \see CompilerOutputter, XmlOutputter, TextOutputter. + */ +class CPPUNIT_API TextTestRunner : public CPPUNIT_NS::TestRunner +{ +public: + TextTestRunner( Outputter *outputter =NULL ); + + virtual ~TextTestRunner(); + + bool run( std::string testPath ="", + bool doWait = false, + bool doPrintResult = true, + bool doPrintProgress = true ); + + void setOutputter( Outputter *outputter ); + + TestResultCollector &result() const; + + TestResult &eventManager() const; + +public: // overridden from TestRunner (to avoid hidden virtual function warning) + virtual void run( TestResult &controller, + const std::string &testPath = "" ); + +protected: + virtual void wait( bool doWait ); + virtual void printResult( bool doPrintResult ); + + TestResultCollector *m_result; + TestResult *m_eventManager; + Outputter *m_outputter; +}; + + +CPPUNIT_NS_END + +#endif // CPPUNIT_UI_TEXT_TEXTTESTRUNNER_H diff --git a/3rdParty/include/msvc6/DSPlugin/TestRunnerDSPluginVC6.h b/3rdParty/include/msvc6/DSPlugin/TestRunnerDSPluginVC6.h new file mode 100644 index 0000000000..c19b4b0759 --- /dev/null +++ b/3rdParty/include/msvc6/DSPlugin/TestRunnerDSPluginVC6.h @@ -0,0 +1,190 @@ +/* this ALWAYS GENERATED file contains the definitions for the interfaces */ + + +/* File created by MIDL compiler version 5.01.0164 */ +/* at Sat Apr 13 11:47:16 2002 + */ +/* Compiler settings for G:\prg\vc\Lib\cppunit\src\msvc6\DSPlugIn\TestRunnerDSPlugin.idl: + Os (OptLev=s), W1, Zp8, env=Win32, ms_ext, c_ext + error checks: allocation ref bounds_check enum stub_data +*/ +//@@MIDL_FILE_HEADING( ) + + +/* verify that the version is high enough to compile this file*/ +#ifndef __REQUIRED_RPCNDR_H_VERSION__ +#define __REQUIRED_RPCNDR_H_VERSION__ 440 +#endif + +#include "rpc.h" +#include "rpcndr.h" + +#ifndef __RPCNDR_H_VERSION__ +#error this stub requires an updated version of +#endif // __RPCNDR_H_VERSION__ + +#ifndef COM_NO_WINDOWS_H +#include "windows.h" +#include "ole2.h" +#endif /*COM_NO_WINDOWS_H*/ + +#ifndef __TestRunnerDSPluginVC6_h__ +#define __TestRunnerDSPluginVC6_h__ + +#ifdef __cplusplus +extern "C"{ +#endif + +/* Forward Declarations */ + +#ifndef __ITestRunnerDSPlugin_FWD_DEFINED__ +#define __ITestRunnerDSPlugin_FWD_DEFINED__ +typedef interface ITestRunnerDSPlugin ITestRunnerDSPlugin; +#endif /* __ITestRunnerDSPlugin_FWD_DEFINED__ */ + + +#ifndef __DSAddIn_FWD_DEFINED__ +#define __DSAddIn_FWD_DEFINED__ + +#ifdef __cplusplus +typedef class DSAddIn DSAddIn; +#else +typedef struct DSAddIn DSAddIn; +#endif /* __cplusplus */ + +#endif /* __DSAddIn_FWD_DEFINED__ */ + + +/* header files for imported files */ +#include "oaidl.h" +#include "ocidl.h" + +void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t); +void __RPC_USER MIDL_user_free( void __RPC_FAR * ); + +#ifndef __ITestRunnerDSPlugin_INTERFACE_DEFINED__ +#define __ITestRunnerDSPlugin_INTERFACE_DEFINED__ + +/* interface ITestRunnerDSPlugin */ +/* [oleautomation][unique][helpstring][uuid][object] */ + + +EXTERN_C const IID IID_ITestRunnerDSPlugin; + +#if defined(__cplusplus) && !defined(CINTERFACE) + + MIDL_INTERFACE("3ADE0E37-5A56-4a68-BD8D-67E9E7502971") + ITestRunnerDSPlugin : public IUnknown + { + public: + virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE goToLineInSourceCode( + /* [in] */ BSTR fileName, + /* [in] */ int lineNumber) = 0; + + }; + +#else /* C style interface */ + + typedef struct ITestRunnerDSPluginVtbl + { + BEGIN_INTERFACE + + HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )( + ITestRunnerDSPlugin __RPC_FAR * This, + /* [in] */ REFIID riid, + /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject); + + ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )( + ITestRunnerDSPlugin __RPC_FAR * This); + + ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )( + ITestRunnerDSPlugin __RPC_FAR * This); + + /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *goToLineInSourceCode )( + ITestRunnerDSPlugin __RPC_FAR * This, + /* [in] */ BSTR fileName, + /* [in] */ int lineNumber); + + END_INTERFACE + } ITestRunnerDSPluginVtbl; + + interface ITestRunnerDSPlugin + { + CONST_VTBL struct ITestRunnerDSPluginVtbl __RPC_FAR *lpVtbl; + }; + + + +#ifdef COBJMACROS + + +#define ITestRunnerDSPlugin_QueryInterface(This,riid,ppvObject) \ + (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) + +#define ITestRunnerDSPlugin_AddRef(This) \ + (This)->lpVtbl -> AddRef(This) + +#define ITestRunnerDSPlugin_Release(This) \ + (This)->lpVtbl -> Release(This) + + +#define ITestRunnerDSPlugin_goToLineInSourceCode(This,fileName,lineNumber) \ + (This)->lpVtbl -> goToLineInSourceCode(This,fileName,lineNumber) + +#endif /* COBJMACROS */ + + +#endif /* C style interface */ + + + +/* [helpstring] */ HRESULT STDMETHODCALLTYPE ITestRunnerDSPlugin_goToLineInSourceCode_Proxy( + ITestRunnerDSPlugin __RPC_FAR * This, + /* [in] */ BSTR fileName, + /* [in] */ int lineNumber); + + +void __RPC_STUB ITestRunnerDSPlugin_goToLineInSourceCode_Stub( + IRpcStubBuffer *This, + IRpcChannelBuffer *_pRpcChannelBuffer, + PRPC_MESSAGE _pRpcMessage, + DWORD *_pdwStubPhase); + + + +#endif /* __ITestRunnerDSPlugin_INTERFACE_DEFINED__ */ + + + +#ifndef __TestRunnerDSPluginLib_LIBRARY_DEFINED__ +#define __TestRunnerDSPluginLib_LIBRARY_DEFINED__ + +/* library TestRunnerDSPluginLib */ +/* [helpstring][version][uuid] */ + + +EXTERN_C const IID LIBID_TestRunnerDSPluginLib; + +EXTERN_C const CLSID CLSID_DSAddIn; + +#ifdef __cplusplus + +class DECLSPEC_UUID("F193CE54-716C-41CB-80B2-FA74CA3EE2AC") +DSAddIn; +#endif +#endif /* __TestRunnerDSPluginLib_LIBRARY_DEFINED__ */ + +/* Additional Prototypes for ALL interfaces */ + +unsigned long __RPC_USER BSTR_UserSize( unsigned long __RPC_FAR *, unsigned long , BSTR __RPC_FAR * ); +unsigned char __RPC_FAR * __RPC_USER BSTR_UserMarshal( unsigned long __RPC_FAR *, unsigned char __RPC_FAR *, BSTR __RPC_FAR * ); +unsigned char __RPC_FAR * __RPC_USER BSTR_UserUnmarshal(unsigned long __RPC_FAR *, unsigned char __RPC_FAR *, BSTR __RPC_FAR * ); +void __RPC_USER BSTR_UserFree( unsigned long __RPC_FAR *, BSTR __RPC_FAR * ); + +/* end of Additional Prototypes */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/3rdParty/include/msvc6/DSPlugin/TestRunnerDSPluginVC6_i.c b/3rdParty/include/msvc6/DSPlugin/TestRunnerDSPluginVC6_i.c new file mode 100644 index 0000000000..9490f9ebd0 --- /dev/null +++ b/3rdParty/include/msvc6/DSPlugin/TestRunnerDSPluginVC6_i.c @@ -0,0 +1,50 @@ +/* this file contains the actual definitions of */ +/* the IIDs and CLSIDs */ + +/* link this file in with the server and any clients */ + + +/* File created by MIDL compiler version 5.01.0164 */ +/* at Sat Apr 13 11:47:16 2002 + */ +/* Compiler settings for G:\prg\vc\Lib\cppunit\src\msvc6\DSPlugIn\TestRunnerDSPlugin.idl: + Os (OptLev=s), W1, Zp8, env=Win32, ms_ext, c_ext + error checks: allocation ref bounds_check enum stub_data +*/ +//@@MIDL_FILE_HEADING( ) +#ifdef __cplusplus +extern "C"{ +#endif + + +#ifndef __IID_DEFINED__ +#define __IID_DEFINED__ + +typedef struct _IID +{ + unsigned long x; + unsigned short s1; + unsigned short s2; + unsigned char c[8]; +} IID; + +#endif // __IID_DEFINED__ + +#ifndef CLSID_DEFINED +#define CLSID_DEFINED +typedef IID CLSID; +#endif // CLSID_DEFINED + +const IID IID_ITestRunnerDSPlugin = {0x3ADE0E37,0x5A56,0x4a68,{0xBD,0x8D,0x67,0xE9,0xE7,0x50,0x29,0x71}}; + + +const IID LIBID_TestRunnerDSPluginLib = {0x3ADE0E38,0x5A56,0x4a68,{0xBD,0x8D,0x67,0xE9,0xE7,0x50,0x29,0x71}}; + + +const CLSID CLSID_DSAddIn = {0xF193CE54,0x716C,0x41CB,{0x80,0xB2,0xFA,0x74,0xCA,0x3E,0xE2,0xAC}}; + + +#ifdef __cplusplus +} +#endif + diff --git a/3rdParty/include/msvc6/USED_WITH_CPPUNIT.txt b/3rdParty/include/msvc6/USED_WITH_CPPUNIT.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/3rdParty/include/msvc6/testrunner/TestPlugInInterface.h b/3rdParty/include/msvc6/testrunner/TestPlugInInterface.h new file mode 100644 index 0000000000..031f2a01f8 --- /dev/null +++ b/3rdParty/include/msvc6/testrunner/TestPlugInInterface.h @@ -0,0 +1,55 @@ +#ifndef CPPUNIT_TESTPLUGINRUNNER_TESTPLUGININTERFACE_H +#define CPPUNIT_TESTPLUGINRUNNER_TESTPLUGININTERFACE_H + +#include +#include + +#if !defined(WINAPI) +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#define NOUSER +#define NOKERNEL +#define NOSOUND +#define NOMINMAX +#include +#endif + +/*! \brief Abstract TestPlugIn for DLL. + * \deprecated Use CppUnitTestPlugIn instead. + * + * A Test plug-in DLL must subclass this class and "publish" an instance + * using the following exported function: + * \code + * extern "C" { + * __declspec(dllimport) TestPlugInInterface *GetTestPlugInInterface(); + * } + * \endcode + * + * When loading the DLL, the TestPlugIn runner look-up this function and + * retreives the + * + * See the TestPlugIn example for VC++ for details. + */ +class TestPlugInInterface +{ +public: + virtual ~TestPlugInInterface() {} + + /*! Returns an instance of the "All Tests" suite. + * + * \return Instance of the top-level suite that contains all test. Ownership + * is granted to the method caller. + */ + virtual CppUnit::Test *makeTest() =0; +}; + +typedef TestPlugInInterface* (WINAPI *GetTestPlugInInterfaceFunction)(void); + + +extern "C" { + __declspec(dllexport) TestPlugInInterface *GetTestPlugInInterface(); +} + + + +#endif // CPPUNIT_TESTPLUGINRUNNER_TESTPLUGININTERFACE_H diff --git a/3rdParty/include/msvc6/testrunner/TestRunner.h b/3rdParty/include/msvc6/testrunner/TestRunner.h new file mode 100644 index 0000000000..0bee97a902 --- /dev/null +++ b/3rdParty/include/msvc6/testrunner/TestRunner.h @@ -0,0 +1,17 @@ +#ifndef CPPUNIT_MSVC_TESTRUNNER_H +#define CPPUNIT_MSVC_TESTRUNNER_H + + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#include + +/*! \brief MFC test runner (DEPRECATED) + * \ingroup ExecutingTest + * \deprecated Use CppUnit::MfcUi::TestRunner instead. + */ +typedef CPPUNIT_NS::MfcTestRunner TestRunner; + +#endif // CPPUNIT_MSVC_TESTRUNNER_H diff --git a/3rdParty/include/sqlite3/sqlite3.h b/3rdParty/include/sqlite3/sqlite3.h new file mode 100644 index 0000000000..9f77592a9d --- /dev/null +++ b/3rdParty/include/sqlite3/sqlite3.h @@ -0,0 +1,5759 @@ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface that the SQLite library +** presents to client programs. If a C-function, structure, datatype, +** or constant definition does not appear in this file, then it is +** not a published API of SQLite, is subject to change without +** notice, and should not be referenced by programs that use SQLite. +** +** Some of the definitions that are in this file are marked as +** "experimental". Experimental interfaces are normally new +** features recently added to SQLite. We do not anticipate changes +** to experimental interfaces but reserve the right to make minor changes +** if experience from use "in the wild" suggest such changes are prudent. +** +** The official C-language API documentation for SQLite is derived +** from comments in this file. This file is the authoritative source +** on how SQLite interfaces are suppose to operate. +** +** The name of this file under configuration management is "sqlite.h.in". +** The makefile makes some minor changes to this file (such as inserting +** the version number) and changes its name to "sqlite3.h" as +** part of the build process. +*/ +#ifndef _SQLITE3_H_ +#define _SQLITE3_H_ +#include /* Needed for the definition of va_list */ + +/* +** Make sure we can call this stuff from C++. +*/ +#ifdef __cplusplus +extern "C" { +#endif + + +/* +** Add the ability to override 'extern' +*/ +#ifndef SQLITE_EXTERN +# define SQLITE_EXTERN extern +#endif + +#ifndef SQLITE_API +# define SQLITE_API +#endif + + +/* +** These no-op macros are used in front of interfaces to mark those +** interfaces as either deprecated or experimental. New applications +** should not use deprecated interfaces - they are support for backwards +** compatibility only. Application writers should be aware that +** experimental interfaces are subject to change in point releases. +** +** These macros used to resolve to various kinds of compiler magic that +** would generate warning messages when they were used. But that +** compiler magic ended up generating such a flurry of bug reports +** that we have taken it all out and gone back to using simple +** noop macros. +*/ +#define SQLITE_DEPRECATED +#define SQLITE_EXPERIMENTAL + +/* +** Ensure these symbols were not defined by some previous header file. +*/ +#ifdef SQLITE_VERSION +# undef SQLITE_VERSION +#endif +#ifdef SQLITE_VERSION_NUMBER +# undef SQLITE_VERSION_NUMBER +#endif + +/* +** CAPI3REF: Compile-Time Library Version Numbers {H10010} +** +** The SQLITE_VERSION and SQLITE_VERSION_NUMBER #defines in +** the sqlite3.h file specify the version of SQLite with which +** that header file is associated. +** +** The "version" of SQLite is a string of the form "W.X.Y" or "W.X.Y.Z". +** The W value is major version number and is always 3 in SQLite3. +** The W value only changes when backwards compatibility is +** broken and we intend to never break backwards compatibility. +** The X value is the minor version number and only changes when +** there are major feature enhancements that are forwards compatible +** but not backwards compatible. +** The Y value is the release number and is incremented with +** each release but resets back to 0 whenever X is incremented. +** The Z value only appears on branch releases. +** +** The SQLITE_VERSION_NUMBER is an integer that is computed as +** follows: +** +**
+** SQLITE_VERSION_NUMBER = W*1000000 + X*1000 + Y
+** 
+** +** Since version 3.6.18, SQLite source code has been stored in the +** fossil configuration management +** system. The SQLITE_SOURCE_ID +** macro is a string which identifies a particular check-in of SQLite +** within its configuration management system. The string contains the +** date and time of the check-in (UTC) and an SHA1 hash of the entire +** source tree. +** +** See also: [sqlite3_libversion()], +** [sqlite3_libversion_number()], [sqlite3_sourceid()], +** [sqlite_version()] and [sqlite_source_id()]. +** +** Requirements: [H10011] [H10014] +*/ +#define SQLITE_VERSION "3.6.18" +#define SQLITE_VERSION_NUMBER 3006018 +#define SQLITE_SOURCE_ID "2009-09-11 14:05:07 b084828a771ec40be85f07c590ca99de4f6c24ee" + +/* +** CAPI3REF: Run-Time Library Version Numbers {H10020} +** KEYWORDS: sqlite3_version +** +** These interfaces provide the same information as the [SQLITE_VERSION], +** [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] #defines in the header, +** but are associated with the library instead of the header file. Cautious +** programmers might include assert() statements in their application to +** verify that values returned by these interfaces match the macros in +** the header, and thus insure that the application is +** compiled with matching library and header files. +** +**
+** assert( sqlite3_libversion_number()==SQLITE_VERSION_NUMBER );
+** assert( strcmp(sqlite3_sourceid(),SQLITE_SOURCE_ID)==0 );
+** assert( strcmp(sqlite3_libversion,SQLITE_VERSION)==0 );
+** 
+** +** The sqlite3_libversion() function returns the same information as is +** in the sqlite3_version[] string constant. The function is provided +** for use in DLLs since DLL users usually do not have direct access to string +** constants within the DLL. Similarly, the sqlite3_sourceid() function +** returns the same information as is in the [SQLITE_SOURCE_ID] #define of +** the header file. +** +** See also: [sqlite_version()] and [sqlite_source_id()]. +** +** Requirements: [H10021] [H10022] [H10023] +*/ +SQLITE_API SQLITE_EXTERN const char sqlite3_version[]; +SQLITE_API const char *sqlite3_libversion(void); +SQLITE_API const char *sqlite3_sourceid(void); +SQLITE_API int sqlite3_libversion_number(void); + +/* +** CAPI3REF: Test To See If The Library Is Threadsafe {H10100} +** +** SQLite can be compiled with or without mutexes. When +** the [SQLITE_THREADSAFE] C preprocessor macro is 1 or 2, mutexes +** are enabled and SQLite is threadsafe. When the +** [SQLITE_THREADSAFE] macro is 0, +** the mutexes are omitted. Without the mutexes, it is not safe +** to use SQLite concurrently from more than one thread. +** +** Enabling mutexes incurs a measurable performance penalty. +** So if speed is of utmost importance, it makes sense to disable +** the mutexes. But for maximum safety, mutexes should be enabled. +** The default behavior is for mutexes to be enabled. +** +** This interface can be used by an application to make sure that the +** version of SQLite that it is linking against was compiled with +** the desired setting of the [SQLITE_THREADSAFE] macro. +** +** This interface only reports on the compile-time mutex setting +** of the [SQLITE_THREADSAFE] flag. If SQLite is compiled with +** SQLITE_THREADSAFE=1 then mutexes are enabled by default but +** can be fully or partially disabled using a call to [sqlite3_config()] +** with the verbs [SQLITE_CONFIG_SINGLETHREAD], [SQLITE_CONFIG_MULTITHREAD], +** or [SQLITE_CONFIG_MUTEX]. The return value of this function shows +** only the default compile-time setting, not any run-time changes +** to that setting. +** +** See the [threading mode] documentation for additional information. +** +** Requirements: [H10101] [H10102] +*/ +SQLITE_API int sqlite3_threadsafe(void); + +/* +** CAPI3REF: Database Connection Handle {H12000} +** KEYWORDS: {database connection} {database connections} +** +** Each open SQLite database is represented by a pointer to an instance of +** the opaque structure named "sqlite3". It is useful to think of an sqlite3 +** pointer as an object. The [sqlite3_open()], [sqlite3_open16()], and +** [sqlite3_open_v2()] interfaces are its constructors, and [sqlite3_close()] +** is its destructor. There are many other interfaces (such as +** [sqlite3_prepare_v2()], [sqlite3_create_function()], and +** [sqlite3_busy_timeout()] to name but three) that are methods on an +** sqlite3 object. +*/ +typedef struct sqlite3 sqlite3; + +/* +** CAPI3REF: 64-Bit Integer Types {H10200} +** KEYWORDS: sqlite_int64 sqlite_uint64 +** +** Because there is no cross-platform way to specify 64-bit integer types +** SQLite includes typedefs for 64-bit signed and unsigned integers. +** +** The sqlite3_int64 and sqlite3_uint64 are the preferred type definitions. +** The sqlite_int64 and sqlite_uint64 types are supported for backwards +** compatibility only. +** +** Requirements: [H10201] [H10202] +*/ +#ifdef SQLITE_INT64_TYPE + typedef SQLITE_INT64_TYPE sqlite_int64; + typedef unsigned SQLITE_INT64_TYPE sqlite_uint64; +#elif defined(_MSC_VER) || defined(__BORLANDC__) + typedef __int64 sqlite_int64; + typedef unsigned __int64 sqlite_uint64; +#else + typedef long long int sqlite_int64; + typedef unsigned long long int sqlite_uint64; +#endif +typedef sqlite_int64 sqlite3_int64; +typedef sqlite_uint64 sqlite3_uint64; + +/* +** If compiling for a processor that lacks floating point support, +** substitute integer for floating-point. +*/ +#ifdef SQLITE_OMIT_FLOATING_POINT +# define double sqlite3_int64 +#endif + +/* +** CAPI3REF: Closing A Database Connection {H12010} +** +** This routine is the destructor for the [sqlite3] object. +** +** Applications should [sqlite3_finalize | finalize] all [prepared statements] +** and [sqlite3_blob_close | close] all [BLOB handles] associated with +** the [sqlite3] object prior to attempting to close the object. +** The [sqlite3_next_stmt()] interface can be used to locate all +** [prepared statements] associated with a [database connection] if desired. +** Typical code might look like this: +** +**
+** sqlite3_stmt *pStmt;
+** while( (pStmt = sqlite3_next_stmt(db, 0))!=0 ){
+**     sqlite3_finalize(pStmt);
+** }
+** 
+** +** If [sqlite3_close()] is invoked while a transaction is open, +** the transaction is automatically rolled back. +** +** The C parameter to [sqlite3_close(C)] must be either a NULL +** pointer or an [sqlite3] object pointer obtained +** from [sqlite3_open()], [sqlite3_open16()], or +** [sqlite3_open_v2()], and not previously closed. +** +** Requirements: +** [H12011] [H12012] [H12013] [H12014] [H12015] [H12019] +*/ +SQLITE_API int sqlite3_close(sqlite3 *); + +/* +** The type for a callback function. +** This is legacy and deprecated. It is included for historical +** compatibility and is not documented. +*/ +typedef int (*sqlite3_callback)(void*,int,char**, char**); + +/* +** CAPI3REF: One-Step Query Execution Interface {H12100} +** +** The sqlite3_exec() interface is a convenient way of running one or more +** SQL statements without having to write a lot of C code. The UTF-8 encoded +** SQL statements are passed in as the second parameter to sqlite3_exec(). +** The statements are evaluated one by one until either an error or +** an interrupt is encountered, or until they are all done. The 3rd parameter +** is an optional callback that is invoked once for each row of any query +** results produced by the SQL statements. The 5th parameter tells where +** to write any error messages. +** +** The error message passed back through the 5th parameter is held +** in memory obtained from [sqlite3_malloc()]. To avoid a memory leak, +** the calling application should call [sqlite3_free()] on any error +** message returned through the 5th parameter when it has finished using +** the error message. +** +** If the SQL statement in the 2nd parameter is NULL or an empty string +** or a string containing only whitespace and comments, then no SQL +** statements are evaluated and the database is not changed. +** +** The sqlite3_exec() interface is implemented in terms of +** [sqlite3_prepare_v2()], [sqlite3_step()], and [sqlite3_finalize()]. +** The sqlite3_exec() routine does nothing to the database that cannot be done +** by [sqlite3_prepare_v2()], [sqlite3_step()], and [sqlite3_finalize()]. +** +** The first parameter to [sqlite3_exec()] must be an valid and open +** [database connection]. +** +** The database connection must not be closed while +** [sqlite3_exec()] is running. +** +** The calling function should use [sqlite3_free()] to free +** the memory that *errmsg is left pointing at once the error +** message is no longer needed. +** +** The SQL statement text in the 2nd parameter to [sqlite3_exec()] +** must remain unchanged while [sqlite3_exec()] is running. +** +** Requirements: +** [H12101] [H12102] [H12104] [H12105] [H12107] [H12110] [H12113] [H12116] +** [H12119] [H12122] [H12125] [H12131] [H12134] [H12137] [H12138] +*/ +SQLITE_API int sqlite3_exec( + sqlite3*, /* An open database */ + const char *sql, /* SQL to be evaluated */ + int (*callback)(void*,int,char**,char**), /* Callback function */ + void *, /* 1st argument to callback */ + char **errmsg /* Error msg written here */ +); + +/* +** CAPI3REF: Result Codes {H10210} +** KEYWORDS: SQLITE_OK {error code} {error codes} +** KEYWORDS: {result code} {result codes} +** +** Many SQLite functions return an integer result code from the set shown +** here in order to indicates success or failure. +** +** New error codes may be added in future versions of SQLite. +** +** See also: [SQLITE_IOERR_READ | extended result codes] +*/ +#define SQLITE_OK 0 /* Successful result */ +/* beginning-of-error-codes */ +#define SQLITE_ERROR 1 /* SQL error or missing database */ +#define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */ +#define SQLITE_PERM 3 /* Access permission denied */ +#define SQLITE_ABORT 4 /* Callback routine requested an abort */ +#define SQLITE_BUSY 5 /* The database file is locked */ +#define SQLITE_LOCKED 6 /* A table in the database is locked */ +#define SQLITE_NOMEM 7 /* A malloc() failed */ +#define SQLITE_READONLY 8 /* Attempt to write a readonly database */ +#define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite3_interrupt()*/ +#define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */ +#define SQLITE_CORRUPT 11 /* The database disk image is malformed */ +#define SQLITE_NOTFOUND 12 /* NOT USED. Table or record not found */ +#define SQLITE_FULL 13 /* Insertion failed because database is full */ +#define SQLITE_CANTOPEN 14 /* Unable to open the database file */ +#define SQLITE_PROTOCOL 15 /* NOT USED. Database lock protocol error */ +#define SQLITE_EMPTY 16 /* Database is empty */ +#define SQLITE_SCHEMA 17 /* The database schema changed */ +#define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */ +#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */ +#define SQLITE_MISMATCH 20 /* Data type mismatch */ +#define SQLITE_MISUSE 21 /* Library used incorrectly */ +#define SQLITE_NOLFS 22 /* Uses OS features not supported on host */ +#define SQLITE_AUTH 23 /* Authorization denied */ +#define SQLITE_FORMAT 24 /* Auxiliary database format error */ +#define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */ +#define SQLITE_NOTADB 26 /* File opened that is not a database file */ +#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */ +#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */ +/* end-of-error-codes */ + +/* +** CAPI3REF: Extended Result Codes {H10220} +** KEYWORDS: {extended error code} {extended error codes} +** KEYWORDS: {extended result code} {extended result codes} +** +** In its default configuration, SQLite API routines return one of 26 integer +** [SQLITE_OK | result codes]. However, experience has shown that many of +** these result codes are too coarse-grained. They do not provide as +** much information about problems as programmers might like. In an effort to +** address this, newer versions of SQLite (version 3.3.8 and later) include +** support for additional result codes that provide more detailed information +** about errors. The extended result codes are enabled or disabled +** on a per database connection basis using the +** [sqlite3_extended_result_codes()] API. +** +** Some of the available extended result codes are listed here. +** One may expect the number of extended result codes will be expand +** over time. Software that uses extended result codes should expect +** to see new result codes in future releases of SQLite. +** +** The SQLITE_OK result code will never be extended. It will always +** be exactly zero. +*/ +#define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8)) +#define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8)) +#define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8)) +#define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4<<8)) +#define SQLITE_IOERR_DIR_FSYNC (SQLITE_IOERR | (5<<8)) +#define SQLITE_IOERR_TRUNCATE (SQLITE_IOERR | (6<<8)) +#define SQLITE_IOERR_FSTAT (SQLITE_IOERR | (7<<8)) +#define SQLITE_IOERR_UNLOCK (SQLITE_IOERR | (8<<8)) +#define SQLITE_IOERR_RDLOCK (SQLITE_IOERR | (9<<8)) +#define SQLITE_IOERR_DELETE (SQLITE_IOERR | (10<<8)) +#define SQLITE_IOERR_BLOCKED (SQLITE_IOERR | (11<<8)) +#define SQLITE_IOERR_NOMEM (SQLITE_IOERR | (12<<8)) +#define SQLITE_IOERR_ACCESS (SQLITE_IOERR | (13<<8)) +#define SQLITE_IOERR_CHECKRESERVEDLOCK (SQLITE_IOERR | (14<<8)) +#define SQLITE_IOERR_LOCK (SQLITE_IOERR | (15<<8)) +#define SQLITE_IOERR_CLOSE (SQLITE_IOERR | (16<<8)) +#define SQLITE_IOERR_DIR_CLOSE (SQLITE_IOERR | (17<<8)) +#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8) ) + +/* +** CAPI3REF: Flags For File Open Operations {H10230} +** +** These bit values are intended for use in the +** 3rd parameter to the [sqlite3_open_v2()] interface and +** in the 4th parameter to the xOpen method of the +** [sqlite3_vfs] object. +*/ +#define SQLITE_OPEN_READONLY 0x00000001 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_CREATE 0x00000004 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_DELETEONCLOSE 0x00000008 /* VFS only */ +#define SQLITE_OPEN_EXCLUSIVE 0x00000010 /* VFS only */ +#define SQLITE_OPEN_MAIN_DB 0x00000100 /* VFS only */ +#define SQLITE_OPEN_TEMP_DB 0x00000200 /* VFS only */ +#define SQLITE_OPEN_TRANSIENT_DB 0x00000400 /* VFS only */ +#define SQLITE_OPEN_MAIN_JOURNAL 0x00000800 /* VFS only */ +#define SQLITE_OPEN_TEMP_JOURNAL 0x00001000 /* VFS only */ +#define SQLITE_OPEN_SUBJOURNAL 0x00002000 /* VFS only */ +#define SQLITE_OPEN_MASTER_JOURNAL 0x00004000 /* VFS only */ +#define SQLITE_OPEN_NOMUTEX 0x00008000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_SHAREDCACHE 0x00020000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_PRIVATECACHE 0x00040000 /* Ok for sqlite3_open_v2() */ + +/* +** CAPI3REF: Device Characteristics {H10240} +** +** The xDeviceCapabilities method of the [sqlite3_io_methods] +** object returns an integer which is a vector of the these +** bit values expressing I/O characteristics of the mass storage +** device that holds the file that the [sqlite3_io_methods] +** refers to. +** +** The SQLITE_IOCAP_ATOMIC property means that all writes of +** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values +** mean that writes of blocks that are nnn bytes in size and +** are aligned to an address which is an integer multiple of +** nnn are atomic. The SQLITE_IOCAP_SAFE_APPEND value means +** that when data is appended to a file, the data is appended +** first then the size of the file is extended, never the other +** way around. The SQLITE_IOCAP_SEQUENTIAL property means that +** information is written to disk in the same order as calls +** to xWrite(). +*/ +#define SQLITE_IOCAP_ATOMIC 0x00000001 +#define SQLITE_IOCAP_ATOMIC512 0x00000002 +#define SQLITE_IOCAP_ATOMIC1K 0x00000004 +#define SQLITE_IOCAP_ATOMIC2K 0x00000008 +#define SQLITE_IOCAP_ATOMIC4K 0x00000010 +#define SQLITE_IOCAP_ATOMIC8K 0x00000020 +#define SQLITE_IOCAP_ATOMIC16K 0x00000040 +#define SQLITE_IOCAP_ATOMIC32K 0x00000080 +#define SQLITE_IOCAP_ATOMIC64K 0x00000100 +#define SQLITE_IOCAP_SAFE_APPEND 0x00000200 +#define SQLITE_IOCAP_SEQUENTIAL 0x00000400 + +/* +** CAPI3REF: File Locking Levels {H10250} +** +** SQLite uses one of these integer values as the second +** argument to calls it makes to the xLock() and xUnlock() methods +** of an [sqlite3_io_methods] object. +*/ +#define SQLITE_LOCK_NONE 0 +#define SQLITE_LOCK_SHARED 1 +#define SQLITE_LOCK_RESERVED 2 +#define SQLITE_LOCK_PENDING 3 +#define SQLITE_LOCK_EXCLUSIVE 4 + +/* +** CAPI3REF: Synchronization Type Flags {H10260} +** +** When SQLite invokes the xSync() method of an +** [sqlite3_io_methods] object it uses a combination of +** these integer values as the second argument. +** +** When the SQLITE_SYNC_DATAONLY flag is used, it means that the +** sync operation only needs to flush data to mass storage. Inode +** information need not be flushed. If the lower four bits of the flag +** equal SQLITE_SYNC_NORMAL, that means to use normal fsync() semantics. +** If the lower four bits equal SQLITE_SYNC_FULL, that means +** to use Mac OS X style fullsync instead of fsync(). +*/ +#define SQLITE_SYNC_NORMAL 0x00002 +#define SQLITE_SYNC_FULL 0x00003 +#define SQLITE_SYNC_DATAONLY 0x00010 + +/* +** CAPI3REF: OS Interface Open File Handle {H11110} +** +** An [sqlite3_file] object represents an open file in the +** [sqlite3_vfs | OS interface layer]. Individual OS interface +** implementations will +** want to subclass this object by appending additional fields +** for their own use. The pMethods entry is a pointer to an +** [sqlite3_io_methods] object that defines methods for performing +** I/O operations on the open file. +*/ +typedef struct sqlite3_file sqlite3_file; +struct sqlite3_file { + const struct sqlite3_io_methods *pMethods; /* Methods for an open file */ +}; + +/* +** CAPI3REF: OS Interface File Virtual Methods Object {H11120} +** +** Every file opened by the [sqlite3_vfs] xOpen method populates an +** [sqlite3_file] object (or, more commonly, a subclass of the +** [sqlite3_file] object) with a pointer to an instance of this object. +** This object defines the methods used to perform various operations +** against the open file represented by the [sqlite3_file] object. +** +** If the xOpen method sets the sqlite3_file.pMethods element +** to a non-NULL pointer, then the sqlite3_io_methods.xClose method +** may be invoked even if the xOpen reported that it failed. The +** only way to prevent a call to xClose following a failed xOpen +** is for the xOpen to set the sqlite3_file.pMethods element to NULL. +** +** The flags argument to xSync may be one of [SQLITE_SYNC_NORMAL] or +** [SQLITE_SYNC_FULL]. The first choice is the normal fsync(). +** The second choice is a Mac OS X style fullsync. The [SQLITE_SYNC_DATAONLY] +** flag may be ORed in to indicate that only the data of the file +** and not its inode needs to be synced. +** +** The integer values to xLock() and xUnlock() are one of +**
    +**
  • [SQLITE_LOCK_NONE], +**
  • [SQLITE_LOCK_SHARED], +**
  • [SQLITE_LOCK_RESERVED], +**
  • [SQLITE_LOCK_PENDING], or +**
  • [SQLITE_LOCK_EXCLUSIVE]. +**
+** xLock() increases the lock. xUnlock() decreases the lock. +** The xCheckReservedLock() method checks whether any database connection, +** either in this process or in some other process, is holding a RESERVED, +** PENDING, or EXCLUSIVE lock on the file. It returns true +** if such a lock exists and false otherwise. +** +** The xFileControl() method is a generic interface that allows custom +** VFS implementations to directly control an open file using the +** [sqlite3_file_control()] interface. The second "op" argument is an +** integer opcode. The third argument is a generic pointer intended to +** point to a structure that may contain arguments or space in which to +** write return values. Potential uses for xFileControl() might be +** functions to enable blocking locks with timeouts, to change the +** locking strategy (for example to use dot-file locks), to inquire +** about the status of a lock, or to break stale locks. The SQLite +** core reserves all opcodes less than 100 for its own use. +** A [SQLITE_FCNTL_LOCKSTATE | list of opcodes] less than 100 is available. +** Applications that define a custom xFileControl method should use opcodes +** greater than 100 to avoid conflicts. +** +** The xSectorSize() method returns the sector size of the +** device that underlies the file. The sector size is the +** minimum write that can be performed without disturbing +** other bytes in the file. The xDeviceCharacteristics() +** method returns a bit vector describing behaviors of the +** underlying device: +** +**
    +**
  • [SQLITE_IOCAP_ATOMIC] +**
  • [SQLITE_IOCAP_ATOMIC512] +**
  • [SQLITE_IOCAP_ATOMIC1K] +**
  • [SQLITE_IOCAP_ATOMIC2K] +**
  • [SQLITE_IOCAP_ATOMIC4K] +**
  • [SQLITE_IOCAP_ATOMIC8K] +**
  • [SQLITE_IOCAP_ATOMIC16K] +**
  • [SQLITE_IOCAP_ATOMIC32K] +**
  • [SQLITE_IOCAP_ATOMIC64K] +**
  • [SQLITE_IOCAP_SAFE_APPEND] +**
  • [SQLITE_IOCAP_SEQUENTIAL] +**
+** +** The SQLITE_IOCAP_ATOMIC property means that all writes of +** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values +** mean that writes of blocks that are nnn bytes in size and +** are aligned to an address which is an integer multiple of +** nnn are atomic. The SQLITE_IOCAP_SAFE_APPEND value means +** that when data is appended to a file, the data is appended +** first then the size of the file is extended, never the other +** way around. The SQLITE_IOCAP_SEQUENTIAL property means that +** information is written to disk in the same order as calls +** to xWrite(). +** +** If xRead() returns SQLITE_IOERR_SHORT_READ it must also fill +** in the unread portions of the buffer with zeros. A VFS that +** fails to zero-fill short reads might seem to work. However, +** failure to zero-fill short reads will eventually lead to +** database corruption. +*/ +typedef struct sqlite3_io_methods sqlite3_io_methods; +struct sqlite3_io_methods { + int iVersion; + int (*xClose)(sqlite3_file*); + int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); + int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst); + int (*xTruncate)(sqlite3_file*, sqlite3_int64 size); + int (*xSync)(sqlite3_file*, int flags); + int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize); + int (*xLock)(sqlite3_file*, int); + int (*xUnlock)(sqlite3_file*, int); + int (*xCheckReservedLock)(sqlite3_file*, int *pResOut); + int (*xFileControl)(sqlite3_file*, int op, void *pArg); + int (*xSectorSize)(sqlite3_file*); + int (*xDeviceCharacteristics)(sqlite3_file*); + /* Additional methods may be added in future releases */ +}; + +/* +** CAPI3REF: Standard File Control Opcodes {H11310} +** +** These integer constants are opcodes for the xFileControl method +** of the [sqlite3_io_methods] object and for the [sqlite3_file_control()] +** interface. +** +** The [SQLITE_FCNTL_LOCKSTATE] opcode is used for debugging. This +** opcode causes the xFileControl method to write the current state of +** the lock (one of [SQLITE_LOCK_NONE], [SQLITE_LOCK_SHARED], +** [SQLITE_LOCK_RESERVED], [SQLITE_LOCK_PENDING], or [SQLITE_LOCK_EXCLUSIVE]) +** into an integer that the pArg argument points to. This capability +** is used during testing and only needs to be supported when SQLITE_TEST +** is defined. +*/ +#define SQLITE_FCNTL_LOCKSTATE 1 +#define SQLITE_GET_LOCKPROXYFILE 2 +#define SQLITE_SET_LOCKPROXYFILE 3 +#define SQLITE_LAST_ERRNO 4 + +/* +** CAPI3REF: Mutex Handle {H17110} +** +** The mutex module within SQLite defines [sqlite3_mutex] to be an +** abstract type for a mutex object. The SQLite core never looks +** at the internal representation of an [sqlite3_mutex]. It only +** deals with pointers to the [sqlite3_mutex] object. +** +** Mutexes are created using [sqlite3_mutex_alloc()]. +*/ +typedef struct sqlite3_mutex sqlite3_mutex; + +/* +** CAPI3REF: OS Interface Object {H11140} +** +** An instance of the sqlite3_vfs object defines the interface between +** the SQLite core and the underlying operating system. The "vfs" +** in the name of the object stands for "virtual file system". +** +** The value of the iVersion field is initially 1 but may be larger in +** future versions of SQLite. Additional fields may be appended to this +** object when the iVersion value is increased. Note that the structure +** of the sqlite3_vfs object changes in the transaction between +** SQLite version 3.5.9 and 3.6.0 and yet the iVersion field was not +** modified. +** +** The szOsFile field is the size of the subclassed [sqlite3_file] +** structure used by this VFS. mxPathname is the maximum length of +** a pathname in this VFS. +** +** Registered sqlite3_vfs objects are kept on a linked list formed by +** the pNext pointer. The [sqlite3_vfs_register()] +** and [sqlite3_vfs_unregister()] interfaces manage this list +** in a thread-safe way. The [sqlite3_vfs_find()] interface +** searches the list. Neither the application code nor the VFS +** implementation should use the pNext pointer. +** +** The pNext field is the only field in the sqlite3_vfs +** structure that SQLite will ever modify. SQLite will only access +** or modify this field while holding a particular static mutex. +** The application should never modify anything within the sqlite3_vfs +** object once the object has been registered. +** +** The zName field holds the name of the VFS module. The name must +** be unique across all VFS modules. +** +** SQLite will guarantee that the zFilename parameter to xOpen +** is either a NULL pointer or string obtained +** from xFullPathname(). SQLite further guarantees that +** the string will be valid and unchanged until xClose() is +** called. Because of the previous sentence, +** the [sqlite3_file] can safely store a pointer to the +** filename if it needs to remember the filename for some reason. +** If the zFilename parameter is xOpen is a NULL pointer then xOpen +** must invent its own temporary name for the file. Whenever the +** xFilename parameter is NULL it will also be the case that the +** flags parameter will include [SQLITE_OPEN_DELETEONCLOSE]. +** +** The flags argument to xOpen() includes all bits set in +** the flags argument to [sqlite3_open_v2()]. Or if [sqlite3_open()] +** or [sqlite3_open16()] is used, then flags includes at least +** [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]. +** If xOpen() opens a file read-only then it sets *pOutFlags to +** include [SQLITE_OPEN_READONLY]. Other bits in *pOutFlags may be set. +** +** SQLite will also add one of the following flags to the xOpen() +** call, depending on the object being opened: +** +**
    +**
  • [SQLITE_OPEN_MAIN_DB] +**
  • [SQLITE_OPEN_MAIN_JOURNAL] +**
  • [SQLITE_OPEN_TEMP_DB] +**
  • [SQLITE_OPEN_TEMP_JOURNAL] +**
  • [SQLITE_OPEN_TRANSIENT_DB] +**
  • [SQLITE_OPEN_SUBJOURNAL] +**
  • [SQLITE_OPEN_MASTER_JOURNAL] +**
+** +** The file I/O implementation can use the object type flags to +** change the way it deals with files. For example, an application +** that does not care about crash recovery or rollback might make +** the open of a journal file a no-op. Writes to this journal would +** also be no-ops, and any attempt to read the journal would return +** SQLITE_IOERR. Or the implementation might recognize that a database +** file will be doing page-aligned sector reads and writes in a random +** order and set up its I/O subsystem accordingly. +** +** SQLite might also add one of the following flags to the xOpen method: +** +**
    +**
  • [SQLITE_OPEN_DELETEONCLOSE] +**
  • [SQLITE_OPEN_EXCLUSIVE] +**
+** +** The [SQLITE_OPEN_DELETEONCLOSE] flag means the file should be +** deleted when it is closed. The [SQLITE_OPEN_DELETEONCLOSE] +** will be set for TEMP databases, journals and for subjournals. +** +** The [SQLITE_OPEN_EXCLUSIVE] flag is always used in conjunction +** with the [SQLITE_OPEN_CREATE] flag, which are both directly +** analogous to the O_EXCL and O_CREAT flags of the POSIX open() +** API. The SQLITE_OPEN_EXCLUSIVE flag, when paired with the +** SQLITE_OPEN_CREATE, is used to indicate that file should always +** be created, and that it is an error if it already exists. +** It is not used to indicate the file should be opened +** for exclusive access. +** +** At least szOsFile bytes of memory are allocated by SQLite +** to hold the [sqlite3_file] structure passed as the third +** argument to xOpen. The xOpen method does not have to +** allocate the structure; it should just fill it in. Note that +** the xOpen method must set the sqlite3_file.pMethods to either +** a valid [sqlite3_io_methods] object or to NULL. xOpen must do +** this even if the open fails. SQLite expects that the sqlite3_file.pMethods +** element will be valid after xOpen returns regardless of the success +** or failure of the xOpen call. +** +** The flags argument to xAccess() may be [SQLITE_ACCESS_EXISTS] +** to test for the existence of a file, or [SQLITE_ACCESS_READWRITE] to +** test whether a file is readable and writable, or [SQLITE_ACCESS_READ] +** to test whether a file is at least readable. The file can be a +** directory. +** +** SQLite will always allocate at least mxPathname+1 bytes for the +** output buffer xFullPathname. The exact size of the output buffer +** is also passed as a parameter to both methods. If the output buffer +** is not large enough, [SQLITE_CANTOPEN] should be returned. Since this is +** handled as a fatal error by SQLite, vfs implementations should endeavor +** to prevent this by setting mxPathname to a sufficiently large value. +** +** The xRandomness(), xSleep(), and xCurrentTime() interfaces +** are not strictly a part of the filesystem, but they are +** included in the VFS structure for completeness. +** The xRandomness() function attempts to return nBytes bytes +** of good-quality randomness into zOut. The return value is +** the actual number of bytes of randomness obtained. +** The xSleep() method causes the calling thread to sleep for at +** least the number of microseconds given. The xCurrentTime() +** method returns a Julian Day Number for the current date and time. +** +*/ +typedef struct sqlite3_vfs sqlite3_vfs; +struct sqlite3_vfs { + int iVersion; /* Structure version number */ + int szOsFile; /* Size of subclassed sqlite3_file */ + int mxPathname; /* Maximum file pathname length */ + sqlite3_vfs *pNext; /* Next registered VFS */ + const char *zName; /* Name of this virtual file system */ + void *pAppData; /* Pointer to application-specific data */ + int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*, + int flags, int *pOutFlags); + int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir); + int (*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut); + int (*xFullPathname)(sqlite3_vfs*, const char *zName, int nOut, char *zOut); + void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename); + void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg); + void (*(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol))(void); + void (*xDlClose)(sqlite3_vfs*, void*); + int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut); + int (*xSleep)(sqlite3_vfs*, int microseconds); + int (*xCurrentTime)(sqlite3_vfs*, double*); + int (*xGetLastError)(sqlite3_vfs*, int, char *); + /* New fields may be appended in figure versions. The iVersion + ** value will increment whenever this happens. */ +}; + +/* +** CAPI3REF: Flags for the xAccess VFS method {H11190} +** +** These integer constants can be used as the third parameter to +** the xAccess method of an [sqlite3_vfs] object. {END} They determine +** what kind of permissions the xAccess method is looking for. +** With SQLITE_ACCESS_EXISTS, the xAccess method +** simply checks whether the file exists. +** With SQLITE_ACCESS_READWRITE, the xAccess method +** checks whether the file is both readable and writable. +** With SQLITE_ACCESS_READ, the xAccess method +** checks whether the file is readable. +*/ +#define SQLITE_ACCESS_EXISTS 0 +#define SQLITE_ACCESS_READWRITE 1 +#define SQLITE_ACCESS_READ 2 + +/* +** CAPI3REF: Initialize The SQLite Library {H10130} +** +** The sqlite3_initialize() routine initializes the +** SQLite library. The sqlite3_shutdown() routine +** deallocates any resources that were allocated by sqlite3_initialize(). +** +** A call to sqlite3_initialize() is an "effective" call if it is +** the first time sqlite3_initialize() is invoked during the lifetime of +** the process, or if it is the first time sqlite3_initialize() is invoked +** following a call to sqlite3_shutdown(). Only an effective call +** of sqlite3_initialize() does any initialization. All other calls +** are harmless no-ops. +** +** A call to sqlite3_shutdown() is an "effective" call if it is the first +** call to sqlite3_shutdown() since the last sqlite3_initialize(). Only +** an effective call to sqlite3_shutdown() does any deinitialization. +** All other calls to sqlite3_shutdown() are harmless no-ops. +** +** Among other things, sqlite3_initialize() shall invoke +** sqlite3_os_init(). Similarly, sqlite3_shutdown() +** shall invoke sqlite3_os_end(). +** +** The sqlite3_initialize() routine returns [SQLITE_OK] on success. +** If for some reason, sqlite3_initialize() is unable to initialize +** the library (perhaps it is unable to allocate a needed resource such +** as a mutex) it returns an [error code] other than [SQLITE_OK]. +** +** The sqlite3_initialize() routine is called internally by many other +** SQLite interfaces so that an application usually does not need to +** invoke sqlite3_initialize() directly. For example, [sqlite3_open()] +** calls sqlite3_initialize() so the SQLite library will be automatically +** initialized when [sqlite3_open()] is called if it has not be initialized +** already. However, if SQLite is compiled with the [SQLITE_OMIT_AUTOINIT] +** compile-time option, then the automatic calls to sqlite3_initialize() +** are omitted and the application must call sqlite3_initialize() directly +** prior to using any other SQLite interface. For maximum portability, +** it is recommended that applications always invoke sqlite3_initialize() +** directly prior to using any other SQLite interface. Future releases +** of SQLite may require this. In other words, the behavior exhibited +** when SQLite is compiled with [SQLITE_OMIT_AUTOINIT] might become the +** default behavior in some future release of SQLite. +** +** The sqlite3_os_init() routine does operating-system specific +** initialization of the SQLite library. The sqlite3_os_end() +** routine undoes the effect of sqlite3_os_init(). Typical tasks +** performed by these routines include allocation or deallocation +** of static resources, initialization of global variables, +** setting up a default [sqlite3_vfs] module, or setting up +** a default configuration using [sqlite3_config()]. +** +** The application should never invoke either sqlite3_os_init() +** or sqlite3_os_end() directly. The application should only invoke +** sqlite3_initialize() and sqlite3_shutdown(). The sqlite3_os_init() +** interface is called automatically by sqlite3_initialize() and +** sqlite3_os_end() is called by sqlite3_shutdown(). Appropriate +** implementations for sqlite3_os_init() and sqlite3_os_end() +** are built into SQLite when it is compiled for Unix, Windows, or OS/2. +** When [custom builds | built for other platforms] +** (using the [SQLITE_OS_OTHER=1] compile-time +** option) the application must supply a suitable implementation for +** sqlite3_os_init() and sqlite3_os_end(). An application-supplied +** implementation of sqlite3_os_init() or sqlite3_os_end() +** must return [SQLITE_OK] on success and some other [error code] upon +** failure. +*/ +SQLITE_API int sqlite3_initialize(void); +SQLITE_API int sqlite3_shutdown(void); +SQLITE_API int sqlite3_os_init(void); +SQLITE_API int sqlite3_os_end(void); + +/* +** CAPI3REF: Configuring The SQLite Library {H14100} +** EXPERIMENTAL +** +** The sqlite3_config() interface is used to make global configuration +** changes to SQLite in order to tune SQLite to the specific needs of +** the application. The default configuration is recommended for most +** applications and so this routine is usually not necessary. It is +** provided to support rare applications with unusual needs. +** +** The sqlite3_config() interface is not threadsafe. The application +** must insure that no other SQLite interfaces are invoked by other +** threads while sqlite3_config() is running. Furthermore, sqlite3_config() +** may only be invoked prior to library initialization using +** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()]. +** Note, however, that sqlite3_config() can be called as part of the +** implementation of an application-defined [sqlite3_os_init()]. +** +** The first argument to sqlite3_config() is an integer +** [SQLITE_CONFIG_SINGLETHREAD | configuration option] that determines +** what property of SQLite is to be configured. Subsequent arguments +** vary depending on the [SQLITE_CONFIG_SINGLETHREAD | configuration option] +** in the first argument. +** +** When a configuration option is set, sqlite3_config() returns [SQLITE_OK]. +** If the option is unknown or SQLite is unable to set the option +** then this routine returns a non-zero [error code]. +** +** Requirements: +** [H14103] [H14106] [H14120] [H14123] [H14126] [H14129] [H14132] [H14135] +** [H14138] [H14141] [H14144] [H14147] [H14150] [H14153] [H14156] [H14159] +** [H14162] [H14165] [H14168] +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_config(int, ...); + +/* +** CAPI3REF: Configure database connections {H14200} +** EXPERIMENTAL +** +** The sqlite3_db_config() interface is used to make configuration +** changes to a [database connection]. The interface is similar to +** [sqlite3_config()] except that the changes apply to a single +** [database connection] (specified in the first argument). The +** sqlite3_db_config() interface can only be used immediately after +** the database connection is created using [sqlite3_open()], +** [sqlite3_open16()], or [sqlite3_open_v2()]. +** +** The second argument to sqlite3_db_config(D,V,...) is the +** configuration verb - an integer code that indicates what +** aspect of the [database connection] is being configured. +** The only choice for this value is [SQLITE_DBCONFIG_LOOKASIDE]. +** New verbs are likely to be added in future releases of SQLite. +** Additional arguments depend on the verb. +** +** Requirements: +** [H14203] [H14206] [H14209] [H14212] [H14215] +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_db_config(sqlite3*, int op, ...); + +/* +** CAPI3REF: Memory Allocation Routines {H10155} +** EXPERIMENTAL +** +** An instance of this object defines the interface between SQLite +** and low-level memory allocation routines. +** +** This object is used in only one place in the SQLite interface. +** A pointer to an instance of this object is the argument to +** [sqlite3_config()] when the configuration option is +** [SQLITE_CONFIG_MALLOC] or [SQLITE_CONFIG_GETMALLOC]. +** By creating an instance of this object +** and passing it to [sqlite3_config]([SQLITE_CONFIG_MALLOC]) +** during configuration, an application can specify an alternative +** memory allocation subsystem for SQLite to use for all of its +** dynamic memory needs. +** +** Note that SQLite comes with several [built-in memory allocators] +** that are perfectly adequate for the overwhelming majority of applications +** and that this object is only useful to a tiny minority of applications +** with specialized memory allocation requirements. This object is +** also used during testing of SQLite in order to specify an alternative +** memory allocator that simulates memory out-of-memory conditions in +** order to verify that SQLite recovers gracefully from such +** conditions. +** +** The xMalloc and xFree methods must work like the +** malloc() and free() functions from the standard C library. +** The xRealloc method must work like realloc() from the standard C library +** with the exception that if the second argument to xRealloc is zero, +** xRealloc must be a no-op - it must not perform any allocation or +** deallocation. SQLite guaranteeds that the second argument to +** xRealloc is always a value returned by a prior call to xRoundup. +** And so in cases where xRoundup always returns a positive number, +** xRealloc can perform exactly as the standard library realloc() and +** still be in compliance with this specification. +** +** xSize should return the allocated size of a memory allocation +** previously obtained from xMalloc or xRealloc. The allocated size +** is always at least as big as the requested size but may be larger. +** +** The xRoundup method returns what would be the allocated size of +** a memory allocation given a particular requested size. Most memory +** allocators round up memory allocations at least to the next multiple +** of 8. Some allocators round up to a larger multiple or to a power of 2. +** Every memory allocation request coming in through [sqlite3_malloc()] +** or [sqlite3_realloc()] first calls xRoundup. If xRoundup returns 0, +** that causes the corresponding memory allocation to fail. +** +** The xInit method initializes the memory allocator. (For example, +** it might allocate any require mutexes or initialize internal data +** structures. The xShutdown method is invoked (indirectly) by +** [sqlite3_shutdown()] and should deallocate any resources acquired +** by xInit. The pAppData pointer is used as the only parameter to +** xInit and xShutdown. +** +** SQLite holds the [SQLITE_MUTEX_STATIC_MASTER] mutex when it invokes +** the xInit method, so the xInit method need not be threadsafe. The +** xShutdown method is only called from [sqlite3_shutdown()] so it does +** not need to be threadsafe either. For all other methods, SQLite +** holds the [SQLITE_MUTEX_STATIC_MEM] mutex as long as the +** [SQLITE_CONFIG_MEMSTATUS] configuration option is turned on (which +** it is by default) and so the methods are automatically serialized. +** However, if [SQLITE_CONFIG_MEMSTATUS] is disabled, then the other +** methods must be threadsafe or else make their own arrangements for +** serialization. +** +** SQLite will never invoke xInit() more than once without an intervening +** call to xShutdown(). +*/ +typedef struct sqlite3_mem_methods sqlite3_mem_methods; +struct sqlite3_mem_methods { + void *(*xMalloc)(int); /* Memory allocation function */ + void (*xFree)(void*); /* Free a prior allocation */ + void *(*xRealloc)(void*,int); /* Resize an allocation */ + int (*xSize)(void*); /* Return the size of an allocation */ + int (*xRoundup)(int); /* Round up request size to allocation size */ + int (*xInit)(void*); /* Initialize the memory allocator */ + void (*xShutdown)(void*); /* Deinitialize the memory allocator */ + void *pAppData; /* Argument to xInit() and xShutdown() */ +}; + +/* +** CAPI3REF: Configuration Options {H10160} +** EXPERIMENTAL +** +** These constants are the available integer configuration options that +** can be passed as the first argument to the [sqlite3_config()] interface. +** +** New configuration options may be added in future releases of SQLite. +** Existing configuration options might be discontinued. Applications +** should check the return code from [sqlite3_config()] to make sure that +** the call worked. The [sqlite3_config()] interface will return a +** non-zero [error code] if a discontinued or unsupported configuration option +** is invoked. +** +**
+**
SQLITE_CONFIG_SINGLETHREAD
+**
There are no arguments to this option. This option disables +** all mutexing and puts SQLite into a mode where it can only be used +** by a single thread.
+** +**
SQLITE_CONFIG_MULTITHREAD
+**
There are no arguments to this option. This option disables +** mutexing on [database connection] and [prepared statement] objects. +** The application is responsible for serializing access to +** [database connections] and [prepared statements]. But other mutexes +** are enabled so that SQLite will be safe to use in a multi-threaded +** environment as long as no two threads attempt to use the same +** [database connection] at the same time. See the [threading mode] +** documentation for additional information.
+** +**
SQLITE_CONFIG_SERIALIZED
+**
There are no arguments to this option. This option enables +** all mutexes including the recursive +** mutexes on [database connection] and [prepared statement] objects. +** In this mode (which is the default when SQLite is compiled with +** [SQLITE_THREADSAFE=1]) the SQLite library will itself serialize access +** to [database connections] and [prepared statements] so that the +** application is free to use the same [database connection] or the +** same [prepared statement] in different threads at the same time. +** See the [threading mode] documentation for additional information.
+** +**
SQLITE_CONFIG_MALLOC
+**
This option takes a single argument which is a pointer to an +** instance of the [sqlite3_mem_methods] structure. The argument specifies +** alternative low-level memory allocation routines to be used in place of +** the memory allocation routines built into SQLite.
+** +**
SQLITE_CONFIG_GETMALLOC
+**
This option takes a single argument which is a pointer to an +** instance of the [sqlite3_mem_methods] structure. The [sqlite3_mem_methods] +** structure is filled with the currently defined memory allocation routines. +** This option can be used to overload the default memory allocation +** routines with a wrapper that simulations memory allocation failure or +** tracks memory usage, for example.
+** +**
SQLITE_CONFIG_MEMSTATUS
+**
This option takes single argument of type int, interpreted as a +** boolean, which enables or disables the collection of memory allocation +** statistics. When disabled, the following SQLite interfaces become +** non-operational: +**
    +**
  • [sqlite3_memory_used()] +**
  • [sqlite3_memory_highwater()] +**
  • [sqlite3_soft_heap_limit()] +**
  • [sqlite3_status()] +**
+**
+** +**
SQLITE_CONFIG_SCRATCH
+**
This option specifies a static memory buffer that SQLite can use for +** scratch memory. There are three arguments: A pointer an 8-byte +** aligned memory buffer from which the scrach allocations will be +** drawn, the size of each scratch allocation (sz), +** and the maximum number of scratch allocations (N). The sz +** argument must be a multiple of 16. The sz parameter should be a few bytes +** larger than the actual scratch space required due to internal overhead. +** The first argument should pointer to an 8-byte aligned buffer +** of at least sz*N bytes of memory. +** SQLite will use no more than one scratch buffer at once per thread, so +** N should be set to the expected maximum number of threads. The sz +** parameter should be 6 times the size of the largest database page size. +** Scratch buffers are used as part of the btree balance operation. If +** The btree balancer needs additional memory beyond what is provided by +** scratch buffers or if no scratch buffer space is specified, then SQLite +** goes to [sqlite3_malloc()] to obtain the memory it needs.
+** +**
SQLITE_CONFIG_PAGECACHE
+**
This option specifies a static memory buffer that SQLite can use for +** the database page cache with the default page cache implemenation. +** This configuration should not be used if an application-define page +** cache implementation is loaded using the SQLITE_CONFIG_PCACHE option. +** There are three arguments to this option: A pointer to 8-byte aligned +** memory, the size of each page buffer (sz), and the number of pages (N). +** The sz argument should be the size of the largest database page +** (a power of two between 512 and 32768) plus a little extra for each +** page header. The page header size is 20 to 40 bytes depending on +** the host architecture. It is harmless, apart from the wasted memory, +** to make sz a little too large. The first +** argument should point to an allocation of at least sz*N bytes of memory. +** SQLite will use the memory provided by the first argument to satisfy its +** memory needs for the first N pages that it adds to cache. If additional +** page cache memory is needed beyond what is provided by this option, then +** SQLite goes to [sqlite3_malloc()] for the additional storage space. +** The implementation might use one or more of the N buffers to hold +** memory accounting information. The pointer in the first argument must +** be aligned to an 8-byte boundary or subsequent behavior of SQLite +** will be undefined.
+** +**
SQLITE_CONFIG_HEAP
+**
This option specifies a static memory buffer that SQLite will use +** for all of its dynamic memory allocation needs beyond those provided +** for by [SQLITE_CONFIG_SCRATCH] and [SQLITE_CONFIG_PAGECACHE]. +** There are three arguments: An 8-byte aligned pointer to the memory, +** the number of bytes in the memory buffer, and the minimum allocation size. +** If the first pointer (the memory pointer) is NULL, then SQLite reverts +** to using its default memory allocator (the system malloc() implementation), +** undoing any prior invocation of [SQLITE_CONFIG_MALLOC]. If the +** memory pointer is not NULL and either [SQLITE_ENABLE_MEMSYS3] or +** [SQLITE_ENABLE_MEMSYS5] are defined, then the alternative memory +** allocator is engaged to handle all of SQLites memory allocation needs. +** The first pointer (the memory pointer) must be aligned to an 8-byte +** boundary or subsequent behavior of SQLite will be undefined.
+** +**
SQLITE_CONFIG_MUTEX
+**
This option takes a single argument which is a pointer to an +** instance of the [sqlite3_mutex_methods] structure. The argument specifies +** alternative low-level mutex routines to be used in place +** the mutex routines built into SQLite.
+** +**
SQLITE_CONFIG_GETMUTEX
+**
This option takes a single argument which is a pointer to an +** instance of the [sqlite3_mutex_methods] structure. The +** [sqlite3_mutex_methods] +** structure is filled with the currently defined mutex routines. +** This option can be used to overload the default mutex allocation +** routines with a wrapper used to track mutex usage for performance +** profiling or testing, for example.
+** +**
SQLITE_CONFIG_LOOKASIDE
+**
This option takes two arguments that determine the default +** memory allocation lookaside optimization. The first argument is the +** size of each lookaside buffer slot and the second is the number of +** slots allocated to each database connection. This option sets the +** default lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE] +** verb to [sqlite3_db_config()] can be used to change the lookaside +** configuration on individual connections.
+** +**
SQLITE_CONFIG_PCACHE
+**
This option takes a single argument which is a pointer to +** an [sqlite3_pcache_methods] object. This object specifies the interface +** to a custom page cache implementation. SQLite makes a copy of the +** object and uses it for page cache memory allocations.
+** +**
SQLITE_CONFIG_GETPCACHE
+**
This option takes a single argument which is a pointer to an +** [sqlite3_pcache_methods] object. SQLite copies of the current +** page cache implementation into that object.
+** +**
+*/ +#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ +#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ +#define SQLITE_CONFIG_SERIALIZED 3 /* nil */ +#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */ +#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */ +#define SQLITE_CONFIG_SCRATCH 6 /* void*, int sz, int N */ +#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */ +#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */ +#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */ +#define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */ +#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */ +/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ +#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */ +#define SQLITE_CONFIG_PCACHE 14 /* sqlite3_pcache_methods* */ +#define SQLITE_CONFIG_GETPCACHE 15 /* sqlite3_pcache_methods* */ + +/* +** CAPI3REF: Configuration Options {H10170} +** EXPERIMENTAL +** +** These constants are the available integer configuration options that +** can be passed as the second argument to the [sqlite3_db_config()] interface. +** +** New configuration options may be added in future releases of SQLite. +** Existing configuration options might be discontinued. Applications +** should check the return code from [sqlite3_db_config()] to make sure that +** the call worked. The [sqlite3_db_config()] interface will return a +** non-zero [error code] if a discontinued or unsupported configuration option +** is invoked. +** +**
+**
SQLITE_DBCONFIG_LOOKASIDE
+**
This option takes three additional arguments that determine the +** [lookaside memory allocator] configuration for the [database connection]. +** The first argument (the third parameter to [sqlite3_db_config()] is a +** pointer to an memory buffer to use for lookaside memory. +** The first argument may be NULL in which case SQLite will allocate the +** lookaside buffer itself using [sqlite3_malloc()]. The second argument is the +** size of each lookaside buffer slot and the third argument is the number of +** slots. The size of the buffer in the first argument must be greater than +** or equal to the product of the second and third arguments. The buffer +** must be aligned to an 8-byte boundary. If the second argument is not +** a multiple of 8, it is internally rounded down to the next smaller +** multiple of 8. See also: [SQLITE_CONFIG_LOOKASIDE]
+** +**
+*/ +#define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ + + +/* +** CAPI3REF: Enable Or Disable Extended Result Codes {H12200} +** +** The sqlite3_extended_result_codes() routine enables or disables the +** [extended result codes] feature of SQLite. The extended result +** codes are disabled by default for historical compatibility considerations. +** +** Requirements: +** [H12201] [H12202] +*/ +SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff); + +/* +** CAPI3REF: Last Insert Rowid {H12220} +** +** Each entry in an SQLite table has a unique 64-bit signed +** integer key called the [ROWID | "rowid"]. The rowid is always available +** as an undeclared column named ROWID, OID, or _ROWID_ as long as those +** names are not also used by explicitly declared columns. If +** the table has a column of type [INTEGER PRIMARY KEY] then that column +** is another alias for the rowid. +** +** This routine returns the [rowid] of the most recent +** successful [INSERT] into the database from the [database connection] +** in the first argument. If no successful [INSERT]s +** have ever occurred on that database connection, zero is returned. +** +** If an [INSERT] occurs within a trigger, then the [rowid] of the inserted +** row is returned by this routine as long as the trigger is running. +** But once the trigger terminates, the value returned by this routine +** reverts to the last value inserted before the trigger fired. +** +** An [INSERT] that fails due to a constraint violation is not a +** successful [INSERT] and does not change the value returned by this +** routine. Thus INSERT OR FAIL, INSERT OR IGNORE, INSERT OR ROLLBACK, +** and INSERT OR ABORT make no changes to the return value of this +** routine when their insertion fails. When INSERT OR REPLACE +** encounters a constraint violation, it does not fail. The +** INSERT continues to completion after deleting rows that caused +** the constraint problem so INSERT OR REPLACE will always change +** the return value of this interface. +** +** For the purposes of this routine, an [INSERT] is considered to +** be successful even if it is subsequently rolled back. +** +** Requirements: +** [H12221] [H12223] +** +** If a separate thread performs a new [INSERT] on the same +** database connection while the [sqlite3_last_insert_rowid()] +** function is running and thus changes the last insert [rowid], +** then the value returned by [sqlite3_last_insert_rowid()] is +** unpredictable and might not equal either the old or the new +** last insert [rowid]. +*/ +SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*); + +/* +** CAPI3REF: Count The Number Of Rows Modified {H12240} +** +** This function returns the number of database rows that were changed +** or inserted or deleted by the most recently completed SQL statement +** on the [database connection] specified by the first parameter. +** Only changes that are directly specified by the [INSERT], [UPDATE], +** or [DELETE] statement are counted. Auxiliary changes caused by +** triggers are not counted. Use the [sqlite3_total_changes()] function +** to find the total number of changes including changes caused by triggers. +** +** Changes to a view that are simulated by an [INSTEAD OF trigger] +** are not counted. Only real table changes are counted. +** +** A "row change" is a change to a single row of a single table +** caused by an INSERT, DELETE, or UPDATE statement. Rows that +** are changed as side effects of [REPLACE] constraint resolution, +** rollback, ABORT processing, [DROP TABLE], or by any other +** mechanisms do not count as direct row changes. +** +** A "trigger context" is a scope of execution that begins and +** ends with the script of a [CREATE TRIGGER | trigger]. +** Most SQL statements are +** evaluated outside of any trigger. This is the "top level" +** trigger context. If a trigger fires from the top level, a +** new trigger context is entered for the duration of that one +** trigger. Subtriggers create subcontexts for their duration. +** +** Calling [sqlite3_exec()] or [sqlite3_step()] recursively does +** not create a new trigger context. +** +** This function returns the number of direct row changes in the +** most recent INSERT, UPDATE, or DELETE statement within the same +** trigger context. +** +** Thus, when called from the top level, this function returns the +** number of changes in the most recent INSERT, UPDATE, or DELETE +** that also occurred at the top level. Within the body of a trigger, +** the sqlite3_changes() interface can be called to find the number of +** changes in the most recently completed INSERT, UPDATE, or DELETE +** statement within the body of the same trigger. +** However, the number returned does not include changes +** caused by subtriggers since those have their own context. +** +** See also the [sqlite3_total_changes()] interface and the +** [count_changes pragma]. +** +** Requirements: +** [H12241] [H12243] +** +** If a separate thread makes changes on the same database connection +** while [sqlite3_changes()] is running then the value returned +** is unpredictable and not meaningful. +*/ +SQLITE_API int sqlite3_changes(sqlite3*); + +/* +** CAPI3REF: Total Number Of Rows Modified {H12260} +** +** This function returns the number of row changes caused by [INSERT], +** [UPDATE] or [DELETE] statements since the [database connection] was opened. +** The count includes all changes from all +** [CREATE TRIGGER | trigger] contexts. However, +** the count does not include changes used to implement [REPLACE] constraints, +** do rollbacks or ABORT processing, or [DROP TABLE] processing. The +** count does not include rows of views that fire an [INSTEAD OF trigger], +** though if the INSTEAD OF trigger makes changes of its own, those changes +** are counted. +** The changes are counted as soon as the statement that makes them is +** completed (when the statement handle is passed to [sqlite3_reset()] or +** [sqlite3_finalize()]). +** +** See also the [sqlite3_changes()] interface and the +** [count_changes pragma]. +** +** Requirements: +** [H12261] [H12263] +** +** If a separate thread makes changes on the same database connection +** while [sqlite3_total_changes()] is running then the value +** returned is unpredictable and not meaningful. +*/ +SQLITE_API int sqlite3_total_changes(sqlite3*); + +/* +** CAPI3REF: Interrupt A Long-Running Query {H12270} +** +** This function causes any pending database operation to abort and +** return at its earliest opportunity. This routine is typically +** called in response to a user action such as pressing "Cancel" +** or Ctrl-C where the user wants a long query operation to halt +** immediately. +** +** It is safe to call this routine from a thread different from the +** thread that is currently running the database operation. But it +** is not safe to call this routine with a [database connection] that +** is closed or might close before sqlite3_interrupt() returns. +** +** If an SQL operation is very nearly finished at the time when +** sqlite3_interrupt() is called, then it might not have an opportunity +** to be interrupted and might continue to completion. +** +** An SQL operation that is interrupted will return [SQLITE_INTERRUPT]. +** If the interrupted SQL operation is an INSERT, UPDATE, or DELETE +** that is inside an explicit transaction, then the entire transaction +** will be rolled back automatically. +** +** The sqlite3_interrupt(D) call is in effect until all currently running +** SQL statements on [database connection] D complete. Any new SQL statements +** that are started after the sqlite3_interrupt() call and before the +** running statements reaches zero are interrupted as if they had been +** running prior to the sqlite3_interrupt() call. New SQL statements +** that are started after the running statement count reaches zero are +** not effected by the sqlite3_interrupt(). +** A call to sqlite3_interrupt(D) that occurs when there are no running +** SQL statements is a no-op and has no effect on SQL statements +** that are started after the sqlite3_interrupt() call returns. +** +** Requirements: +** [H12271] [H12272] +** +** If the database connection closes while [sqlite3_interrupt()] +** is running then bad things will likely happen. +*/ +SQLITE_API void sqlite3_interrupt(sqlite3*); + +/* +** CAPI3REF: Determine If An SQL Statement Is Complete {H10510} +** +** These routines are useful during command-line input to determine if the +** currently entered text seems to form a complete SQL statement or +** if additional input is needed before sending the text into +** SQLite for parsing. These routines return 1 if the input string +** appears to be a complete SQL statement. A statement is judged to be +** complete if it ends with a semicolon token and is not a prefix of a +** well-formed CREATE TRIGGER statement. Semicolons that are embedded within +** string literals or quoted identifier names or comments are not +** independent tokens (they are part of the token in which they are +** embedded) and thus do not count as a statement terminator. Whitespace +** and comments that follow the final semicolon are ignored. +** +** These routines return 0 if the statement is incomplete. If a +** memory allocation fails, then SQLITE_NOMEM is returned. +** +** These routines do not parse the SQL statements thus +** will not detect syntactically incorrect SQL. +** +** If SQLite has not been initialized using [sqlite3_initialize()] prior +** to invoking sqlite3_complete16() then sqlite3_initialize() is invoked +** automatically by sqlite3_complete16(). If that initialization fails, +** then the return value from sqlite3_complete16() will be non-zero +** regardless of whether or not the input SQL is complete. +** +** Requirements: [H10511] [H10512] +** +** The input to [sqlite3_complete()] must be a zero-terminated +** UTF-8 string. +** +** The input to [sqlite3_complete16()] must be a zero-terminated +** UTF-16 string in native byte order. +*/ +SQLITE_API int sqlite3_complete(const char *sql); +SQLITE_API int sqlite3_complete16(const void *sql); + +/* +** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors {H12310} +** +** This routine sets a callback function that might be invoked whenever +** an attempt is made to open a database table that another thread +** or process has locked. +** +** If the busy callback is NULL, then [SQLITE_BUSY] or [SQLITE_IOERR_BLOCKED] +** is returned immediately upon encountering the lock. If the busy callback +** is not NULL, then the callback will be invoked with two arguments. +** +** The first argument to the handler is a copy of the void* pointer which +** is the third argument to sqlite3_busy_handler(). The second argument to +** the handler callback is the number of times that the busy handler has +** been invoked for this locking event. If the +** busy callback returns 0, then no additional attempts are made to +** access the database and [SQLITE_BUSY] or [SQLITE_IOERR_BLOCKED] is returned. +** If the callback returns non-zero, then another attempt +** is made to open the database for reading and the cycle repeats. +** +** The presence of a busy handler does not guarantee that it will be invoked +** when there is lock contention. If SQLite determines that invoking the busy +** handler could result in a deadlock, it will go ahead and return [SQLITE_BUSY] +** or [SQLITE_IOERR_BLOCKED] instead of invoking the busy handler. +** Consider a scenario where one process is holding a read lock that +** it is trying to promote to a reserved lock and +** a second process is holding a reserved lock that it is trying +** to promote to an exclusive lock. The first process cannot proceed +** because it is blocked by the second and the second process cannot +** proceed because it is blocked by the first. If both processes +** invoke the busy handlers, neither will make any progress. Therefore, +** SQLite returns [SQLITE_BUSY] for the first process, hoping that this +** will induce the first process to release its read lock and allow +** the second process to proceed. +** +** The default busy callback is NULL. +** +** The [SQLITE_BUSY] error is converted to [SQLITE_IOERR_BLOCKED] +** when SQLite is in the middle of a large transaction where all the +** changes will not fit into the in-memory cache. SQLite will +** already hold a RESERVED lock on the database file, but it needs +** to promote this lock to EXCLUSIVE so that it can spill cache +** pages into the database file without harm to concurrent +** readers. If it is unable to promote the lock, then the in-memory +** cache will be left in an inconsistent state and so the error +** code is promoted from the relatively benign [SQLITE_BUSY] to +** the more severe [SQLITE_IOERR_BLOCKED]. This error code promotion +** forces an automatic rollback of the changes. See the +** +** CorruptionFollowingBusyError wiki page for a discussion of why +** this is important. +** +** There can only be a single busy handler defined for each +** [database connection]. Setting a new busy handler clears any +** previously set handler. Note that calling [sqlite3_busy_timeout()] +** will also set or clear the busy handler. +** +** The busy callback should not take any actions which modify the +** database connection that invoked the busy handler. Any such actions +** result in undefined behavior. +** +** Requirements: +** [H12311] [H12312] [H12314] [H12316] [H12318] +** +** A busy handler must not close the database connection +** or [prepared statement] that invoked the busy handler. +*/ +SQLITE_API int sqlite3_busy_handler(sqlite3*, int(*)(void*,int), void*); + +/* +** CAPI3REF: Set A Busy Timeout {H12340} +** +** This routine sets a [sqlite3_busy_handler | busy handler] that sleeps +** for a specified amount of time when a table is locked. The handler +** will sleep multiple times until at least "ms" milliseconds of sleeping +** have accumulated. {H12343} After "ms" milliseconds of sleeping, +** the handler returns 0 which causes [sqlite3_step()] to return +** [SQLITE_BUSY] or [SQLITE_IOERR_BLOCKED]. +** +** Calling this routine with an argument less than or equal to zero +** turns off all busy handlers. +** +** There can only be a single busy handler for a particular +** [database connection] any any given moment. If another busy handler +** was defined (using [sqlite3_busy_handler()]) prior to calling +** this routine, that other busy handler is cleared. +** +** Requirements: +** [H12341] [H12343] [H12344] +*/ +SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms); + +/* +** CAPI3REF: Convenience Routines For Running Queries {H12370} +** +** Definition: A result table is memory data structure created by the +** [sqlite3_get_table()] interface. A result table records the +** complete query results from one or more queries. +** +** The table conceptually has a number of rows and columns. But +** these numbers are not part of the result table itself. These +** numbers are obtained separately. Let N be the number of rows +** and M be the number of columns. +** +** A result table is an array of pointers to zero-terminated UTF-8 strings. +** There are (N+1)*M elements in the array. The first M pointers point +** to zero-terminated strings that contain the names of the columns. +** The remaining entries all point to query results. NULL values result +** in NULL pointers. All other values are in their UTF-8 zero-terminated +** string representation as returned by [sqlite3_column_text()]. +** +** A result table might consist of one or more memory allocations. +** It is not safe to pass a result table directly to [sqlite3_free()]. +** A result table should be deallocated using [sqlite3_free_table()]. +** +** As an example of the result table format, suppose a query result +** is as follows: +** +**
+**        Name        | Age
+**        -----------------------
+**        Alice       | 43
+**        Bob         | 28
+**        Cindy       | 21
+** 
+** +** There are two column (M==2) and three rows (N==3). Thus the +** result table has 8 entries. Suppose the result table is stored +** in an array names azResult. Then azResult holds this content: +** +**
+**        azResult[0] = "Name";
+**        azResult[1] = "Age";
+**        azResult[2] = "Alice";
+**        azResult[3] = "43";
+**        azResult[4] = "Bob";
+**        azResult[5] = "28";
+**        azResult[6] = "Cindy";
+**        azResult[7] = "21";
+** 
+** +** The sqlite3_get_table() function evaluates one or more +** semicolon-separated SQL statements in the zero-terminated UTF-8 +** string of its 2nd parameter. It returns a result table to the +** pointer given in its 3rd parameter. +** +** After the calling function has finished using the result, it should +** pass the pointer to the result table to sqlite3_free_table() in order to +** release the memory that was malloced. Because of the way the +** [sqlite3_malloc()] happens within sqlite3_get_table(), the calling +** function must not try to call [sqlite3_free()] directly. Only +** [sqlite3_free_table()] is able to release the memory properly and safely. +** +** The sqlite3_get_table() interface is implemented as a wrapper around +** [sqlite3_exec()]. The sqlite3_get_table() routine does not have access +** to any internal data structures of SQLite. It uses only the public +** interface defined here. As a consequence, errors that occur in the +** wrapper layer outside of the internal [sqlite3_exec()] call are not +** reflected in subsequent calls to [sqlite3_errcode()] or [sqlite3_errmsg()]. +** +** Requirements: +** [H12371] [H12373] [H12374] [H12376] [H12379] [H12382] +*/ +SQLITE_API int sqlite3_get_table( + sqlite3 *db, /* An open database */ + const char *zSql, /* SQL to be evaluated */ + char ***pazResult, /* Results of the query */ + int *pnRow, /* Number of result rows written here */ + int *pnColumn, /* Number of result columns written here */ + char **pzErrmsg /* Error msg written here */ +); +SQLITE_API void sqlite3_free_table(char **result); + +/* +** CAPI3REF: Formatted String Printing Functions {H17400} +** +** These routines are work-alikes of the "printf()" family of functions +** from the standard C library. +** +** The sqlite3_mprintf() and sqlite3_vmprintf() routines write their +** results into memory obtained from [sqlite3_malloc()]. +** The strings returned by these two routines should be +** released by [sqlite3_free()]. Both routines return a +** NULL pointer if [sqlite3_malloc()] is unable to allocate enough +** memory to hold the resulting string. +** +** In sqlite3_snprintf() routine is similar to "snprintf()" from +** the standard C library. The result is written into the +** buffer supplied as the second parameter whose size is given by +** the first parameter. Note that the order of the +** first two parameters is reversed from snprintf(). This is an +** historical accident that cannot be fixed without breaking +** backwards compatibility. Note also that sqlite3_snprintf() +** returns a pointer to its buffer instead of the number of +** characters actually written into the buffer. We admit that +** the number of characters written would be a more useful return +** value but we cannot change the implementation of sqlite3_snprintf() +** now without breaking compatibility. +** +** As long as the buffer size is greater than zero, sqlite3_snprintf() +** guarantees that the buffer is always zero-terminated. The first +** parameter "n" is the total size of the buffer, including space for +** the zero terminator. So the longest string that can be completely +** written will be n-1 characters. +** +** These routines all implement some additional formatting +** options that are useful for constructing SQL statements. +** All of the usual printf() formatting options apply. In addition, there +** is are "%q", "%Q", and "%z" options. +** +** The %q option works like %s in that it substitutes a null-terminated +** string from the argument list. But %q also doubles every '\'' character. +** %q is designed for use inside a string literal. By doubling each '\'' +** character it escapes that character and allows it to be inserted into +** the string. +** +** For example, assume the string variable zText contains text as follows: +** +**
+**  char *zText = "It's a happy day!";
+** 
+** +** One can use this text in an SQL statement as follows: +** +**
+**  char *zSQL = sqlite3_mprintf("INSERT INTO table VALUES('%q')", zText);
+**  sqlite3_exec(db, zSQL, 0, 0, 0);
+**  sqlite3_free(zSQL);
+** 
+** +** Because the %q format string is used, the '\'' character in zText +** is escaped and the SQL generated is as follows: +** +**
+**  INSERT INTO table1 VALUES('It''s a happy day!')
+** 
+** +** This is correct. Had we used %s instead of %q, the generated SQL +** would have looked like this: +** +**
+**  INSERT INTO table1 VALUES('It's a happy day!');
+** 
+** +** This second example is an SQL syntax error. As a general rule you should +** always use %q instead of %s when inserting text into a string literal. +** +** The %Q option works like %q except it also adds single quotes around +** the outside of the total string. Additionally, if the parameter in the +** argument list is a NULL pointer, %Q substitutes the text "NULL" (without +** single quotes) in place of the %Q option. So, for example, one could say: +** +**
+**  char *zSQL = sqlite3_mprintf("INSERT INTO table VALUES(%Q)", zText);
+**  sqlite3_exec(db, zSQL, 0, 0, 0);
+**  sqlite3_free(zSQL);
+** 
+** +** The code above will render a correct SQL statement in the zSQL +** variable even if the zText variable is a NULL pointer. +** +** The "%z" formatting option works exactly like "%s" with the +** addition that after the string has been read and copied into +** the result, [sqlite3_free()] is called on the input string. {END} +** +** Requirements: +** [H17403] [H17406] [H17407] +*/ +SQLITE_API char *sqlite3_mprintf(const char*,...); +SQLITE_API char *sqlite3_vmprintf(const char*, va_list); +SQLITE_API char *sqlite3_snprintf(int,char*,const char*, ...); + +/* +** CAPI3REF: Memory Allocation Subsystem {H17300} +** +** The SQLite core uses these three routines for all of its own +** internal memory allocation needs. "Core" in the previous sentence +** does not include operating-system specific VFS implementation. The +** Windows VFS uses native malloc() and free() for some operations. +** +** The sqlite3_malloc() routine returns a pointer to a block +** of memory at least N bytes in length, where N is the parameter. +** If sqlite3_malloc() is unable to obtain sufficient free +** memory, it returns a NULL pointer. If the parameter N to +** sqlite3_malloc() is zero or negative then sqlite3_malloc() returns +** a NULL pointer. +** +** Calling sqlite3_free() with a pointer previously returned +** by sqlite3_malloc() or sqlite3_realloc() releases that memory so +** that it might be reused. The sqlite3_free() routine is +** a no-op if is called with a NULL pointer. Passing a NULL pointer +** to sqlite3_free() is harmless. After being freed, memory +** should neither be read nor written. Even reading previously freed +** memory might result in a segmentation fault or other severe error. +** Memory corruption, a segmentation fault, or other severe error +** might result if sqlite3_free() is called with a non-NULL pointer that +** was not obtained from sqlite3_malloc() or sqlite3_realloc(). +** +** The sqlite3_realloc() interface attempts to resize a +** prior memory allocation to be at least N bytes, where N is the +** second parameter. The memory allocation to be resized is the first +** parameter. If the first parameter to sqlite3_realloc() +** is a NULL pointer then its behavior is identical to calling +** sqlite3_malloc(N) where N is the second parameter to sqlite3_realloc(). +** If the second parameter to sqlite3_realloc() is zero or +** negative then the behavior is exactly the same as calling +** sqlite3_free(P) where P is the first parameter to sqlite3_realloc(). +** sqlite3_realloc() returns a pointer to a memory allocation +** of at least N bytes in size or NULL if sufficient memory is unavailable. +** If M is the size of the prior allocation, then min(N,M) bytes +** of the prior allocation are copied into the beginning of buffer returned +** by sqlite3_realloc() and the prior allocation is freed. +** If sqlite3_realloc() returns NULL, then the prior allocation +** is not freed. +** +** The memory returned by sqlite3_malloc() and sqlite3_realloc() +** is always aligned to at least an 8 byte boundary. {END} +** +** The default implementation of the memory allocation subsystem uses +** the malloc(), realloc() and free() provided by the standard C library. +** {H17382} However, if SQLite is compiled with the +** SQLITE_MEMORY_SIZE=NNN C preprocessor macro (where NNN +** is an integer), then SQLite create a static array of at least +** NNN bytes in size and uses that array for all of its dynamic +** memory allocation needs. {END} Additional memory allocator options +** may be added in future releases. +** +** In SQLite version 3.5.0 and 3.5.1, it was possible to define +** the SQLITE_OMIT_MEMORY_ALLOCATION which would cause the built-in +** implementation of these routines to be omitted. That capability +** is no longer provided. Only built-in memory allocators can be used. +** +** The Windows OS interface layer calls +** the system malloc() and free() directly when converting +** filenames between the UTF-8 encoding used by SQLite +** and whatever filename encoding is used by the particular Windows +** installation. Memory allocation errors are detected, but +** they are reported back as [SQLITE_CANTOPEN] or +** [SQLITE_IOERR] rather than [SQLITE_NOMEM]. +** +** Requirements: +** [H17303] [H17304] [H17305] [H17306] [H17310] [H17312] [H17315] [H17318] +** [H17321] [H17322] [H17323] +** +** The pointer arguments to [sqlite3_free()] and [sqlite3_realloc()] +** must be either NULL or else pointers obtained from a prior +** invocation of [sqlite3_malloc()] or [sqlite3_realloc()] that have +** not yet been released. +** +** The application must not read or write any part of +** a block of memory after it has been released using +** [sqlite3_free()] or [sqlite3_realloc()]. +*/ +SQLITE_API void *sqlite3_malloc(int); +SQLITE_API void *sqlite3_realloc(void*, int); +SQLITE_API void sqlite3_free(void*); + +/* +** CAPI3REF: Memory Allocator Statistics {H17370} +** +** SQLite provides these two interfaces for reporting on the status +** of the [sqlite3_malloc()], [sqlite3_free()], and [sqlite3_realloc()] +** routines, which form the built-in memory allocation subsystem. +** +** Requirements: +** [H17371] [H17373] [H17374] [H17375] +*/ +SQLITE_API sqlite3_int64 sqlite3_memory_used(void); +SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag); + +/* +** CAPI3REF: Pseudo-Random Number Generator {H17390} +** +** SQLite contains a high-quality pseudo-random number generator (PRNG) used to +** select random [ROWID | ROWIDs] when inserting new records into a table that +** already uses the largest possible [ROWID]. The PRNG is also used for +** the build-in random() and randomblob() SQL functions. This interface allows +** applications to access the same PRNG for other purposes. +** +** A call to this routine stores N bytes of randomness into buffer P. +** +** The first time this routine is invoked (either internally or by +** the application) the PRNG is seeded using randomness obtained +** from the xRandomness method of the default [sqlite3_vfs] object. +** On all subsequent invocations, the pseudo-randomness is generated +** internally and without recourse to the [sqlite3_vfs] xRandomness +** method. +** +** Requirements: +** [H17392] +*/ +SQLITE_API void sqlite3_randomness(int N, void *P); + +/* +** CAPI3REF: Compile-Time Authorization Callbacks {H12500} +** +** This routine registers a authorizer callback with a particular +** [database connection], supplied in the first argument. +** The authorizer callback is invoked as SQL statements are being compiled +** by [sqlite3_prepare()] or its variants [sqlite3_prepare_v2()], +** [sqlite3_prepare16()] and [sqlite3_prepare16_v2()]. At various +** points during the compilation process, as logic is being created +** to perform various actions, the authorizer callback is invoked to +** see if those actions are allowed. The authorizer callback should +** return [SQLITE_OK] to allow the action, [SQLITE_IGNORE] to disallow the +** specific action but allow the SQL statement to continue to be +** compiled, or [SQLITE_DENY] to cause the entire SQL statement to be +** rejected with an error. If the authorizer callback returns +** any value other than [SQLITE_IGNORE], [SQLITE_OK], or [SQLITE_DENY] +** then the [sqlite3_prepare_v2()] or equivalent call that triggered +** the authorizer will fail with an error message. +** +** When the callback returns [SQLITE_OK], that means the operation +** requested is ok. When the callback returns [SQLITE_DENY], the +** [sqlite3_prepare_v2()] or equivalent call that triggered the +** authorizer will fail with an error message explaining that +** access is denied. +** +** The first parameter to the authorizer callback is a copy of the third +** parameter to the sqlite3_set_authorizer() interface. The second parameter +** to the callback is an integer [SQLITE_COPY | action code] that specifies +** the particular action to be authorized. The third through sixth parameters +** to the callback are zero-terminated strings that contain additional +** details about the action to be authorized. +** +** If the action code is [SQLITE_READ] +** and the callback returns [SQLITE_IGNORE] then the +** [prepared statement] statement is constructed to substitute +** a NULL value in place of the table column that would have +** been read if [SQLITE_OK] had been returned. The [SQLITE_IGNORE] +** return can be used to deny an untrusted user access to individual +** columns of a table. +** If the action code is [SQLITE_DELETE] and the callback returns +** [SQLITE_IGNORE] then the [DELETE] operation proceeds but the +** [truncate optimization] is disabled and all rows are deleted individually. +** +** An authorizer is used when [sqlite3_prepare | preparing] +** SQL statements from an untrusted source, to ensure that the SQL statements +** do not try to access data they are not allowed to see, or that they do not +** try to execute malicious statements that damage the database. For +** example, an application may allow a user to enter arbitrary +** SQL queries for evaluation by a database. But the application does +** not want the user to be able to make arbitrary changes to the +** database. An authorizer could then be put in place while the +** user-entered SQL is being [sqlite3_prepare | prepared] that +** disallows everything except [SELECT] statements. +** +** Applications that need to process SQL from untrusted sources +** might also consider lowering resource limits using [sqlite3_limit()] +** and limiting database size using the [max_page_count] [PRAGMA] +** in addition to using an authorizer. +** +** Only a single authorizer can be in place on a database connection +** at a time. Each call to sqlite3_set_authorizer overrides the +** previous call. Disable the authorizer by installing a NULL callback. +** The authorizer is disabled by default. +** +** The authorizer callback must not do anything that will modify +** the database connection that invoked the authorizer callback. +** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their +** database connections for the meaning of "modify" in this paragraph. +** +** When [sqlite3_prepare_v2()] is used to prepare a statement, the +** statement might be re-prepared during [sqlite3_step()] due to a +** schema change. Hence, the application should ensure that the +** correct authorizer callback remains in place during the [sqlite3_step()]. +** +** Note that the authorizer callback is invoked only during +** [sqlite3_prepare()] or its variants. Authorization is not +** performed during statement evaluation in [sqlite3_step()], unless +** as stated in the previous paragraph, sqlite3_step() invokes +** sqlite3_prepare_v2() to reprepare a statement after a schema change. +** +** Requirements: +** [H12501] [H12502] [H12503] [H12504] [H12505] [H12506] [H12507] [H12510] +** [H12511] [H12512] [H12520] [H12521] [H12522] +*/ +SQLITE_API int sqlite3_set_authorizer( + sqlite3*, + int (*xAuth)(void*,int,const char*,const char*,const char*,const char*), + void *pUserData +); + +/* +** CAPI3REF: Authorizer Return Codes {H12590} +** +** The [sqlite3_set_authorizer | authorizer callback function] must +** return either [SQLITE_OK] or one of these two constants in order +** to signal SQLite whether or not the action is permitted. See the +** [sqlite3_set_authorizer | authorizer documentation] for additional +** information. +*/ +#define SQLITE_DENY 1 /* Abort the SQL statement with an error */ +#define SQLITE_IGNORE 2 /* Don't allow access, but don't generate an error */ + +/* +** CAPI3REF: Authorizer Action Codes {H12550} +** +** The [sqlite3_set_authorizer()] interface registers a callback function +** that is invoked to authorize certain SQL statement actions. The +** second parameter to the callback is an integer code that specifies +** what action is being authorized. These are the integer action codes that +** the authorizer callback may be passed. +** +** These action code values signify what kind of operation is to be +** authorized. The 3rd and 4th parameters to the authorization +** callback function will be parameters or NULL depending on which of these +** codes is used as the second parameter. The 5th parameter to the +** authorizer callback is the name of the database ("main", "temp", +** etc.) if applicable. The 6th parameter to the authorizer callback +** is the name of the inner-most trigger or view that is responsible for +** the access attempt or NULL if this access attempt is directly from +** top-level SQL code. +** +** Requirements: +** [H12551] [H12552] [H12553] [H12554] +*/ +/******************************************* 3rd ************ 4th ***********/ +#define SQLITE_CREATE_INDEX 1 /* Index Name Table Name */ +#define SQLITE_CREATE_TABLE 2 /* Table Name NULL */ +#define SQLITE_CREATE_TEMP_INDEX 3 /* Index Name Table Name */ +#define SQLITE_CREATE_TEMP_TABLE 4 /* Table Name NULL */ +#define SQLITE_CREATE_TEMP_TRIGGER 5 /* Trigger Name Table Name */ +#define SQLITE_CREATE_TEMP_VIEW 6 /* View Name NULL */ +#define SQLITE_CREATE_TRIGGER 7 /* Trigger Name Table Name */ +#define SQLITE_CREATE_VIEW 8 /* View Name NULL */ +#define SQLITE_DELETE 9 /* Table Name NULL */ +#define SQLITE_DROP_INDEX 10 /* Index Name Table Name */ +#define SQLITE_DROP_TABLE 11 /* Table Name NULL */ +#define SQLITE_DROP_TEMP_INDEX 12 /* Index Name Table Name */ +#define SQLITE_DROP_TEMP_TABLE 13 /* Table Name NULL */ +#define SQLITE_DROP_TEMP_TRIGGER 14 /* Trigger Name Table Name */ +#define SQLITE_DROP_TEMP_VIEW 15 /* View Name NULL */ +#define SQLITE_DROP_TRIGGER 16 /* Trigger Name Table Name */ +#define SQLITE_DROP_VIEW 17 /* View Name NULL */ +#define SQLITE_INSERT 18 /* Table Name NULL */ +#define SQLITE_PRAGMA 19 /* Pragma Name 1st arg or NULL */ +#define SQLITE_READ 20 /* Table Name Column Name */ +#define SQLITE_SELECT 21 /* NULL NULL */ +#define SQLITE_TRANSACTION 22 /* Operation NULL */ +#define SQLITE_UPDATE 23 /* Table Name Column Name */ +#define SQLITE_ATTACH 24 /* Filename NULL */ +#define SQLITE_DETACH 25 /* Database Name NULL */ +#define SQLITE_ALTER_TABLE 26 /* Database Name Table Name */ +#define SQLITE_REINDEX 27 /* Index Name NULL */ +#define SQLITE_ANALYZE 28 /* Table Name NULL */ +#define SQLITE_CREATE_VTABLE 29 /* Table Name Module Name */ +#define SQLITE_DROP_VTABLE 30 /* Table Name Module Name */ +#define SQLITE_FUNCTION 31 /* NULL Function Name */ +#define SQLITE_SAVEPOINT 32 /* Operation Savepoint Name */ +#define SQLITE_COPY 0 /* No longer used */ + +/* +** CAPI3REF: Tracing And Profiling Functions {H12280} +** EXPERIMENTAL +** +** These routines register callback functions that can be used for +** tracing and profiling the execution of SQL statements. +** +** The callback function registered by sqlite3_trace() is invoked at +** various times when an SQL statement is being run by [sqlite3_step()]. +** The callback returns a UTF-8 rendering of the SQL statement text +** as the statement first begins executing. Additional callbacks occur +** as each triggered subprogram is entered. The callbacks for triggers +** contain a UTF-8 SQL comment that identifies the trigger. +** +** The callback function registered by sqlite3_profile() is invoked +** as each SQL statement finishes. The profile callback contains +** the original statement text and an estimate of wall-clock time +** of how long that statement took to run. +** +** Requirements: +** [H12281] [H12282] [H12283] [H12284] [H12285] [H12287] [H12288] [H12289] +** [H12290] +*/ +SQLITE_API SQLITE_EXPERIMENTAL void *sqlite3_trace(sqlite3*, void(*xTrace)(void*,const char*), void*); +SQLITE_API SQLITE_EXPERIMENTAL void *sqlite3_profile(sqlite3*, + void(*xProfile)(void*,const char*,sqlite3_uint64), void*); + +/* +** CAPI3REF: Query Progress Callbacks {H12910} +** +** This routine configures a callback function - the +** progress callback - that is invoked periodically during long +** running calls to [sqlite3_exec()], [sqlite3_step()] and +** [sqlite3_get_table()]. An example use for this +** interface is to keep a GUI updated during a large query. +** +** If the progress callback returns non-zero, the operation is +** interrupted. This feature can be used to implement a +** "Cancel" button on a GUI progress dialog box. +** +** The progress handler must not do anything that will modify +** the database connection that invoked the progress handler. +** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their +** database connections for the meaning of "modify" in this paragraph. +** +** Requirements: +** [H12911] [H12912] [H12913] [H12914] [H12915] [H12916] [H12917] [H12918] +** +*/ +SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); + +/* +** CAPI3REF: Opening A New Database Connection {H12700} +** +** These routines open an SQLite database file whose name is given by the +** filename argument. The filename argument is interpreted as UTF-8 for +** sqlite3_open() and sqlite3_open_v2() and as UTF-16 in the native byte +** order for sqlite3_open16(). A [database connection] handle is usually +** returned in *ppDb, even if an error occurs. The only exception is that +** if SQLite is unable to allocate memory to hold the [sqlite3] object, +** a NULL will be written into *ppDb instead of a pointer to the [sqlite3] +** object. If the database is opened (and/or created) successfully, then +** [SQLITE_OK] is returned. Otherwise an [error code] is returned. The +** [sqlite3_errmsg()] or [sqlite3_errmsg16()] routines can be used to obtain +** an English language description of the error. +** +** The default encoding for the database will be UTF-8 if +** sqlite3_open() or sqlite3_open_v2() is called and +** UTF-16 in the native byte order if sqlite3_open16() is used. +** +** Whether or not an error occurs when it is opened, resources +** associated with the [database connection] handle should be released by +** passing it to [sqlite3_close()] when it is no longer required. +** +** The sqlite3_open_v2() interface works like sqlite3_open() +** except that it accepts two additional parameters for additional control +** over the new database connection. The flags parameter can take one of +** the following three values, optionally combined with the +** [SQLITE_OPEN_NOMUTEX], [SQLITE_OPEN_FULLMUTEX], [SQLITE_OPEN_SHAREDCACHE], +** and/or [SQLITE_OPEN_PRIVATECACHE] flags: +** +**
+**
[SQLITE_OPEN_READONLY]
+**
The database is opened in read-only mode. If the database does not +** already exist, an error is returned.
+** +**
[SQLITE_OPEN_READWRITE]
+**
The database is opened for reading and writing if possible, or reading +** only if the file is write protected by the operating system. In either +** case the database must already exist, otherwise an error is returned.
+** +**
[SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]
+**
The database is opened for reading and writing, and is creates it if +** it does not already exist. This is the behavior that is always used for +** sqlite3_open() and sqlite3_open16().
+**
+** +** If the 3rd parameter to sqlite3_open_v2() is not one of the +** combinations shown above or one of the combinations shown above combined +** with the [SQLITE_OPEN_NOMUTEX], [SQLITE_OPEN_FULLMUTEX], +** [SQLITE_OPEN_SHAREDCACHE] and/or [SQLITE_OPEN_SHAREDCACHE] flags, +** then the behavior is undefined. +** +** If the [SQLITE_OPEN_NOMUTEX] flag is set, then the database connection +** opens in the multi-thread [threading mode] as long as the single-thread +** mode has not been set at compile-time or start-time. If the +** [SQLITE_OPEN_FULLMUTEX] flag is set then the database connection opens +** in the serialized [threading mode] unless single-thread was +** previously selected at compile-time or start-time. +** The [SQLITE_OPEN_SHAREDCACHE] flag causes the database connection to be +** eligible to use [shared cache mode], regardless of whether or not shared +** cache is enabled using [sqlite3_enable_shared_cache()]. The +** [SQLITE_OPEN_PRIVATECACHE] flag causes the database connection to not +** participate in [shared cache mode] even if it is enabled. +** +** If the filename is ":memory:", then a private, temporary in-memory database +** is created for the connection. This in-memory database will vanish when +** the database connection is closed. Future versions of SQLite might +** make use of additional special filenames that begin with the ":" character. +** It is recommended that when a database filename actually does begin with +** a ":" character you should prefix the filename with a pathname such as +** "./" to avoid ambiguity. +** +** If the filename is an empty string, then a private, temporary +** on-disk database will be created. This private database will be +** automatically deleted as soon as the database connection is closed. +** +** The fourth parameter to sqlite3_open_v2() is the name of the +** [sqlite3_vfs] object that defines the operating system interface that +** the new database connection should use. If the fourth parameter is +** a NULL pointer then the default [sqlite3_vfs] object is used. +** +** Note to Windows users: The encoding used for the filename argument +** of sqlite3_open() and sqlite3_open_v2() must be UTF-8, not whatever +** codepage is currently defined. Filenames containing international +** characters must be converted to UTF-8 prior to passing them into +** sqlite3_open() or sqlite3_open_v2(). +** +** Requirements: +** [H12701] [H12702] [H12703] [H12704] [H12706] [H12707] [H12709] [H12711] +** [H12712] [H12713] [H12714] [H12717] [H12719] [H12721] [H12723] +*/ +SQLITE_API int sqlite3_open( + const char *filename, /* Database filename (UTF-8) */ + sqlite3 **ppDb /* OUT: SQLite db handle */ +); +SQLITE_API int sqlite3_open16( + const void *filename, /* Database filename (UTF-16) */ + sqlite3 **ppDb /* OUT: SQLite db handle */ +); +SQLITE_API int sqlite3_open_v2( + const char *filename, /* Database filename (UTF-8) */ + sqlite3 **ppDb, /* OUT: SQLite db handle */ + int flags, /* Flags */ + const char *zVfs /* Name of VFS module to use */ +); + +/* +** CAPI3REF: Error Codes And Messages {H12800} +** +** The sqlite3_errcode() interface returns the numeric [result code] or +** [extended result code] for the most recent failed sqlite3_* API call +** associated with a [database connection]. If a prior API call failed +** but the most recent API call succeeded, the return value from +** sqlite3_errcode() is undefined. The sqlite3_extended_errcode() +** interface is the same except that it always returns the +** [extended result code] even when extended result codes are +** disabled. +** +** The sqlite3_errmsg() and sqlite3_errmsg16() return English-language +** text that describes the error, as either UTF-8 or UTF-16 respectively. +** Memory to hold the error message string is managed internally. +** The application does not need to worry about freeing the result. +** However, the error string might be overwritten or deallocated by +** subsequent calls to other SQLite interface functions. +** +** When the serialized [threading mode] is in use, it might be the +** case that a second error occurs on a separate thread in between +** the time of the first error and the call to these interfaces. +** When that happens, the second error will be reported since these +** interfaces always report the most recent result. To avoid +** this, each thread can obtain exclusive use of the [database connection] D +** by invoking [sqlite3_mutex_enter]([sqlite3_db_mutex](D)) before beginning +** to use D and invoking [sqlite3_mutex_leave]([sqlite3_db_mutex](D)) after +** all calls to the interfaces listed here are completed. +** +** If an interface fails with SQLITE_MISUSE, that means the interface +** was invoked incorrectly by the application. In that case, the +** error code and message may or may not be set. +** +** Requirements: +** [H12801] [H12802] [H12803] [H12807] [H12808] [H12809] +*/ +SQLITE_API int sqlite3_errcode(sqlite3 *db); +SQLITE_API int sqlite3_extended_errcode(sqlite3 *db); +SQLITE_API const char *sqlite3_errmsg(sqlite3*); +SQLITE_API const void *sqlite3_errmsg16(sqlite3*); + +/* +** CAPI3REF: SQL Statement Object {H13000} +** KEYWORDS: {prepared statement} {prepared statements} +** +** An instance of this object represents a single SQL statement. +** This object is variously known as a "prepared statement" or a +** "compiled SQL statement" or simply as a "statement". +** +** The life of a statement object goes something like this: +** +**
    +**
  1. Create the object using [sqlite3_prepare_v2()] or a related +** function. +**
  2. Bind values to [host parameters] using the sqlite3_bind_*() +** interfaces. +**
  3. Run the SQL by calling [sqlite3_step()] one or more times. +**
  4. Reset the statement using [sqlite3_reset()] then go back +** to step 2. Do this zero or more times. +**
  5. Destroy the object using [sqlite3_finalize()]. +**
+** +** Refer to documentation on individual methods above for additional +** information. +*/ +typedef struct sqlite3_stmt sqlite3_stmt; + +/* +** CAPI3REF: Run-time Limits {H12760} +** +** This interface allows the size of various constructs to be limited +** on a connection by connection basis. The first parameter is the +** [database connection] whose limit is to be set or queried. The +** second parameter is one of the [limit categories] that define a +** class of constructs to be size limited. The third parameter is the +** new limit for that construct. The function returns the old limit. +** +** If the new limit is a negative number, the limit is unchanged. +** For the limit category of SQLITE_LIMIT_XYZ there is a +** [limits | hard upper bound] +** set by a compile-time C preprocessor macro named +** [limits | SQLITE_MAX_XYZ]. +** (The "_LIMIT_" in the name is changed to "_MAX_".) +** Attempts to increase a limit above its hard upper bound are +** silently truncated to the hard upper limit. +** +** Run time limits are intended for use in applications that manage +** both their own internal database and also databases that are controlled +** by untrusted external sources. An example application might be a +** web browser that has its own databases for storing history and +** separate databases controlled by JavaScript applications downloaded +** off the Internet. The internal databases can be given the +** large, default limits. Databases managed by external sources can +** be given much smaller limits designed to prevent a denial of service +** attack. Developers might also want to use the [sqlite3_set_authorizer()] +** interface to further control untrusted SQL. The size of the database +** created by an untrusted script can be contained using the +** [max_page_count] [PRAGMA]. +** +** New run-time limit categories may be added in future releases. +** +** Requirements: +** [H12762] [H12766] [H12769] +*/ +SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); + +/* +** CAPI3REF: Run-Time Limit Categories {H12790} +** KEYWORDS: {limit category} {limit categories} +** +** These constants define various performance limits +** that can be lowered at run-time using [sqlite3_limit()]. +** The synopsis of the meanings of the various limits is shown below. +** Additional information is available at [limits | Limits in SQLite]. +** +**
+**
SQLITE_LIMIT_LENGTH
+**
The maximum size of any string or BLOB or table row.
+** +**
SQLITE_LIMIT_SQL_LENGTH
+**
The maximum length of an SQL statement.
+** +**
SQLITE_LIMIT_COLUMN
+**
The maximum number of columns in a table definition or in the +** result set of a [SELECT] or the maximum number of columns in an index +** or in an ORDER BY or GROUP BY clause.
+** +**
SQLITE_LIMIT_EXPR_DEPTH
+**
The maximum depth of the parse tree on any expression.
+** +**
SQLITE_LIMIT_COMPOUND_SELECT
+**
The maximum number of terms in a compound SELECT statement.
+** +**
SQLITE_LIMIT_VDBE_OP
+**
The maximum number of instructions in a virtual machine program +** used to implement an SQL statement.
+** +**
SQLITE_LIMIT_FUNCTION_ARG
+**
The maximum number of arguments on a function.
+** +**
SQLITE_LIMIT_ATTACHED
+**
The maximum number of [ATTACH | attached databases].
+** +**
SQLITE_LIMIT_LIKE_PATTERN_LENGTH
+**
The maximum length of the pattern argument to the [LIKE] or +** [GLOB] operators.
+** +**
SQLITE_LIMIT_VARIABLE_NUMBER
+**
The maximum number of variables in an SQL statement that can +** be bound.
+** +**
SQLITE_LIMIT_TRIGGER_DEPTH
+**
The maximum depth of recursion for triggers.
+**
+*/ +#define SQLITE_LIMIT_LENGTH 0 +#define SQLITE_LIMIT_SQL_LENGTH 1 +#define SQLITE_LIMIT_COLUMN 2 +#define SQLITE_LIMIT_EXPR_DEPTH 3 +#define SQLITE_LIMIT_COMPOUND_SELECT 4 +#define SQLITE_LIMIT_VDBE_OP 5 +#define SQLITE_LIMIT_FUNCTION_ARG 6 +#define SQLITE_LIMIT_ATTACHED 7 +#define SQLITE_LIMIT_LIKE_PATTERN_LENGTH 8 +#define SQLITE_LIMIT_VARIABLE_NUMBER 9 +#define SQLITE_LIMIT_TRIGGER_DEPTH 10 + +/* +** CAPI3REF: Compiling An SQL Statement {H13010} +** KEYWORDS: {SQL statement compiler} +** +** To execute an SQL query, it must first be compiled into a byte-code +** program using one of these routines. +** +** The first argument, "db", is a [database connection] obtained from a +** prior successful call to [sqlite3_open()], [sqlite3_open_v2()] or +** [sqlite3_open16()]. The database connection must not have been closed. +** +** The second argument, "zSql", is the statement to be compiled, encoded +** as either UTF-8 or UTF-16. The sqlite3_prepare() and sqlite3_prepare_v2() +** interfaces use UTF-8, and sqlite3_prepare16() and sqlite3_prepare16_v2() +** use UTF-16. +** +** If the nByte argument is less than zero, then zSql is read up to the +** first zero terminator. If nByte is non-negative, then it is the maximum +** number of bytes read from zSql. When nByte is non-negative, the +** zSql string ends at either the first '\000' or '\u0000' character or +** the nByte-th byte, whichever comes first. If the caller knows +** that the supplied string is nul-terminated, then there is a small +** performance advantage to be gained by passing an nByte parameter that +** is equal to the number of bytes in the input string including +** the nul-terminator bytes. +** +** If pzTail is not NULL then *pzTail is made to point to the first byte +** past the end of the first SQL statement in zSql. These routines only +** compile the first statement in zSql, so *pzTail is left pointing to +** what remains uncompiled. +** +** *ppStmt is left pointing to a compiled [prepared statement] that can be +** executed using [sqlite3_step()]. If there is an error, *ppStmt is set +** to NULL. If the input text contains no SQL (if the input is an empty +** string or a comment) then *ppStmt is set to NULL. +** The calling procedure is responsible for deleting the compiled +** SQL statement using [sqlite3_finalize()] after it has finished with it. +** ppStmt may not be NULL. +** +** On success, [SQLITE_OK] is returned, otherwise an [error code] is returned. +** +** The sqlite3_prepare_v2() and sqlite3_prepare16_v2() interfaces are +** recommended for all new programs. The two older interfaces are retained +** for backwards compatibility, but their use is discouraged. +** In the "v2" interfaces, the prepared statement +** that is returned (the [sqlite3_stmt] object) contains a copy of the +** original SQL text. This causes the [sqlite3_step()] interface to +** behave a differently in two ways: +** +**
    +**
  1. +** If the database schema changes, instead of returning [SQLITE_SCHEMA] as it +** always used to do, [sqlite3_step()] will automatically recompile the SQL +** statement and try to run it again. If the schema has changed in +** a way that makes the statement no longer valid, [sqlite3_step()] will still +** return [SQLITE_SCHEMA]. But unlike the legacy behavior, [SQLITE_SCHEMA] is +** now a fatal error. Calling [sqlite3_prepare_v2()] again will not make the +** error go away. Note: use [sqlite3_errmsg()] to find the text +** of the parsing error that results in an [SQLITE_SCHEMA] return. +**
  2. +** +**
  3. +** When an error occurs, [sqlite3_step()] will return one of the detailed +** [error codes] or [extended error codes]. The legacy behavior was that +** [sqlite3_step()] would only return a generic [SQLITE_ERROR] result code +** and you would have to make a second call to [sqlite3_reset()] in order +** to find the underlying cause of the problem. With the "v2" prepare +** interfaces, the underlying reason for the error is returned immediately. +**
  4. +**
+** +** Requirements: +** [H13011] [H13012] [H13013] [H13014] [H13015] [H13016] [H13019] [H13021] +** +*/ +SQLITE_API int sqlite3_prepare( + sqlite3 *db, /* Database handle */ + const char *zSql, /* SQL statement, UTF-8 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const char **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare_v2( + sqlite3 *db, /* Database handle */ + const char *zSql, /* SQL statement, UTF-8 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const char **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare16( + sqlite3 *db, /* Database handle */ + const void *zSql, /* SQL statement, UTF-16 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const void **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare16_v2( + sqlite3 *db, /* Database handle */ + const void *zSql, /* SQL statement, UTF-16 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const void **pzTail /* OUT: Pointer to unused portion of zSql */ +); + +/* +** CAPI3REF: Retrieving Statement SQL {H13100} +** +** This interface can be used to retrieve a saved copy of the original +** SQL text used to create a [prepared statement] if that statement was +** compiled using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()]. +** +** Requirements: +** [H13101] [H13102] [H13103] +*/ +SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Dynamically Typed Value Object {H15000} +** KEYWORDS: {protected sqlite3_value} {unprotected sqlite3_value} +** +** SQLite uses the sqlite3_value object to represent all values +** that can be stored in a database table. SQLite uses dynamic typing +** for the values it stores. Values stored in sqlite3_value objects +** can be integers, floating point values, strings, BLOBs, or NULL. +** +** An sqlite3_value object may be either "protected" or "unprotected". +** Some interfaces require a protected sqlite3_value. Other interfaces +** will accept either a protected or an unprotected sqlite3_value. +** Every interface that accepts sqlite3_value arguments specifies +** whether or not it requires a protected sqlite3_value. +** +** The terms "protected" and "unprotected" refer to whether or not +** a mutex is held. A internal mutex is held for a protected +** sqlite3_value object but no mutex is held for an unprotected +** sqlite3_value object. If SQLite is compiled to be single-threaded +** (with [SQLITE_THREADSAFE=0] and with [sqlite3_threadsafe()] returning 0) +** or if SQLite is run in one of reduced mutex modes +** [SQLITE_CONFIG_SINGLETHREAD] or [SQLITE_CONFIG_MULTITHREAD] +** then there is no distinction between protected and unprotected +** sqlite3_value objects and they can be used interchangeably. However, +** for maximum code portability it is recommended that applications +** still make the distinction between between protected and unprotected +** sqlite3_value objects even when not strictly required. +** +** The sqlite3_value objects that are passed as parameters into the +** implementation of [application-defined SQL functions] are protected. +** The sqlite3_value object returned by +** [sqlite3_column_value()] is unprotected. +** Unprotected sqlite3_value objects may only be used with +** [sqlite3_result_value()] and [sqlite3_bind_value()]. +** The [sqlite3_value_blob | sqlite3_value_type()] family of +** interfaces require protected sqlite3_value objects. +*/ +typedef struct Mem sqlite3_value; + +/* +** CAPI3REF: SQL Function Context Object {H16001} +** +** The context in which an SQL function executes is stored in an +** sqlite3_context object. A pointer to an sqlite3_context object +** is always first parameter to [application-defined SQL functions]. +** The application-defined SQL function implementation will pass this +** pointer through into calls to [sqlite3_result_int | sqlite3_result()], +** [sqlite3_aggregate_context()], [sqlite3_user_data()], +** [sqlite3_context_db_handle()], [sqlite3_get_auxdata()], +** and/or [sqlite3_set_auxdata()]. +*/ +typedef struct sqlite3_context sqlite3_context; + +/* +** CAPI3REF: Binding Values To Prepared Statements {H13500} +** KEYWORDS: {host parameter} {host parameters} {host parameter name} +** KEYWORDS: {SQL parameter} {SQL parameters} {parameter binding} +** +** In the SQL strings input to [sqlite3_prepare_v2()] and its variants, +** literals may be replaced by a [parameter] that matches one of following +** templates: +** +**
    +**
  • ? +**
  • ?NNN +**
  • :VVV +**
  • @VVV +**
  • $VVV +**
+** +** In the templates above, NNN represents an integer literal, +** and VVV represents an alphanumeric identifer. The values of these +** parameters (also called "host parameter names" or "SQL parameters") +** can be set using the sqlite3_bind_*() routines defined here. +** +** The first argument to the sqlite3_bind_*() routines is always +** a pointer to the [sqlite3_stmt] object returned from +** [sqlite3_prepare_v2()] or its variants. +** +** The second argument is the index of the SQL parameter to be set. +** The leftmost SQL parameter has an index of 1. When the same named +** SQL parameter is used more than once, second and subsequent +** occurrences have the same index as the first occurrence. +** The index for named parameters can be looked up using the +** [sqlite3_bind_parameter_index()] API if desired. The index +** for "?NNN" parameters is the value of NNN. +** The NNN value must be between 1 and the [sqlite3_limit()] +** parameter [SQLITE_LIMIT_VARIABLE_NUMBER] (default value: 999). +** +** The third argument is the value to bind to the parameter. +** +** In those routines that have a fourth argument, its value is the +** number of bytes in the parameter. To be clear: the value is the +** number of bytes in the value, not the number of characters. +** If the fourth parameter is negative, the length of the string is +** the number of bytes up to the first zero terminator. +** +** The fifth argument to sqlite3_bind_blob(), sqlite3_bind_text(), and +** sqlite3_bind_text16() is a destructor used to dispose of the BLOB or +** string after SQLite has finished with it. If the fifth argument is +** the special value [SQLITE_STATIC], then SQLite assumes that the +** information is in static, unmanaged space and does not need to be freed. +** If the fifth argument has the value [SQLITE_TRANSIENT], then +** SQLite makes its own private copy of the data immediately, before +** the sqlite3_bind_*() routine returns. +** +** The sqlite3_bind_zeroblob() routine binds a BLOB of length N that +** is filled with zeroes. A zeroblob uses a fixed amount of memory +** (just an integer to hold its size) while it is being processed. +** Zeroblobs are intended to serve as placeholders for BLOBs whose +** content is later written using +** [sqlite3_blob_open | incremental BLOB I/O] routines. +** A negative value for the zeroblob results in a zero-length BLOB. +** +** The sqlite3_bind_*() routines must be called after +** [sqlite3_prepare_v2()] (and its variants) or [sqlite3_reset()] and +** before [sqlite3_step()]. +** Bindings are not cleared by the [sqlite3_reset()] routine. +** Unbound parameters are interpreted as NULL. +** +** These routines return [SQLITE_OK] on success or an error code if +** anything goes wrong. [SQLITE_RANGE] is returned if the parameter +** index is out of range. [SQLITE_NOMEM] is returned if malloc() fails. +** [SQLITE_MISUSE] might be returned if these routines are called on a +** virtual machine that is the wrong state or which has already been finalized. +** Detection of misuse is unreliable. Applications should not depend +** on SQLITE_MISUSE returns. SQLITE_MISUSE is intended to indicate a +** a logic error in the application. Future versions of SQLite might +** panic rather than return SQLITE_MISUSE. +** +** See also: [sqlite3_bind_parameter_count()], +** [sqlite3_bind_parameter_name()], and [sqlite3_bind_parameter_index()]. +** +** Requirements: +** [H13506] [H13509] [H13512] [H13515] [H13518] [H13521] [H13524] [H13527] +** [H13530] [H13533] [H13536] [H13539] [H13542] [H13545] [H13548] [H13551] +** +*/ +SQLITE_API int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*)); +SQLITE_API int sqlite3_bind_double(sqlite3_stmt*, int, double); +SQLITE_API int sqlite3_bind_int(sqlite3_stmt*, int, int); +SQLITE_API int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64); +SQLITE_API int sqlite3_bind_null(sqlite3_stmt*, int); +SQLITE_API int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*)); +SQLITE_API int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*)); +SQLITE_API int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); +SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n); + +/* +** CAPI3REF: Number Of SQL Parameters {H13600} +** +** This routine can be used to find the number of [SQL parameters] +** in a [prepared statement]. SQL parameters are tokens of the +** form "?", "?NNN", ":AAA", "$AAA", or "@AAA" that serve as +** placeholders for values that are [sqlite3_bind_blob | bound] +** to the parameters at a later time. +** +** This routine actually returns the index of the largest (rightmost) +** parameter. For all forms except ?NNN, this will correspond to the +** number of unique parameters. If parameters of the ?NNN are used, +** there may be gaps in the list. +** +** See also: [sqlite3_bind_blob|sqlite3_bind()], +** [sqlite3_bind_parameter_name()], and +** [sqlite3_bind_parameter_index()]. +** +** Requirements: +** [H13601] +*/ +SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt*); + +/* +** CAPI3REF: Name Of A Host Parameter {H13620} +** +** This routine returns a pointer to the name of the n-th +** [SQL parameter] in a [prepared statement]. +** SQL parameters of the form "?NNN" or ":AAA" or "@AAA" or "$AAA" +** have a name which is the string "?NNN" or ":AAA" or "@AAA" or "$AAA" +** respectively. +** In other words, the initial ":" or "$" or "@" or "?" +** is included as part of the name. +** Parameters of the form "?" without a following integer have no name +** and are also referred to as "anonymous parameters". +** +** The first host parameter has an index of 1, not 0. +** +** If the value n is out of range or if the n-th parameter is +** nameless, then NULL is returned. The returned string is +** always in UTF-8 encoding even if the named parameter was +** originally specified as UTF-16 in [sqlite3_prepare16()] or +** [sqlite3_prepare16_v2()]. +** +** See also: [sqlite3_bind_blob|sqlite3_bind()], +** [sqlite3_bind_parameter_count()], and +** [sqlite3_bind_parameter_index()]. +** +** Requirements: +** [H13621] +*/ +SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt*, int); + +/* +** CAPI3REF: Index Of A Parameter With A Given Name {H13640} +** +** Return the index of an SQL parameter given its name. The +** index value returned is suitable for use as the second +** parameter to [sqlite3_bind_blob|sqlite3_bind()]. A zero +** is returned if no matching parameter is found. The parameter +** name must be given in UTF-8 even if the original statement +** was prepared from UTF-16 text using [sqlite3_prepare16_v2()]. +** +** See also: [sqlite3_bind_blob|sqlite3_bind()], +** [sqlite3_bind_parameter_count()], and +** [sqlite3_bind_parameter_index()]. +** +** Requirements: +** [H13641] +*/ +SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName); + +/* +** CAPI3REF: Reset All Bindings On A Prepared Statement {H13660} +** +** Contrary to the intuition of many, [sqlite3_reset()] does not reset +** the [sqlite3_bind_blob | bindings] on a [prepared statement]. +** Use this routine to reset all host parameters to NULL. +** +** Requirements: +** [H13661] +*/ +SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt*); + +/* +** CAPI3REF: Number Of Columns In A Result Set {H13710} +** +** Return the number of columns in the result set returned by the +** [prepared statement]. This routine returns 0 if pStmt is an SQL +** statement that does not return data (for example an [UPDATE]). +** +** Requirements: +** [H13711] +*/ +SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Column Names In A Result Set {H13720} +** +** These routines return the name assigned to a particular column +** in the result set of a [SELECT] statement. The sqlite3_column_name() +** interface returns a pointer to a zero-terminated UTF-8 string +** and sqlite3_column_name16() returns a pointer to a zero-terminated +** UTF-16 string. The first parameter is the [prepared statement] +** that implements the [SELECT] statement. The second parameter is the +** column number. The leftmost column is number 0. +** +** The returned string pointer is valid until either the [prepared statement] +** is destroyed by [sqlite3_finalize()] or until the next call to +** sqlite3_column_name() or sqlite3_column_name16() on the same column. +** +** If sqlite3_malloc() fails during the processing of either routine +** (for example during a conversion from UTF-8 to UTF-16) then a +** NULL pointer is returned. +** +** The name of a result column is the value of the "AS" clause for +** that column, if there is an AS clause. If there is no AS clause +** then the name of the column is unspecified and may change from +** one release of SQLite to the next. +** +** Requirements: +** [H13721] [H13723] [H13724] [H13725] [H13726] [H13727] +*/ +SQLITE_API const char *sqlite3_column_name(sqlite3_stmt*, int N); +SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N); + +/* +** CAPI3REF: Source Of Data In A Query Result {H13740} +** +** These routines provide a means to determine what column of what +** table in which database a result of a [SELECT] statement comes from. +** The name of the database or table or column can be returned as +** either a UTF-8 or UTF-16 string. The _database_ routines return +** the database name, the _table_ routines return the table name, and +** the origin_ routines return the column name. +** The returned string is valid until the [prepared statement] is destroyed +** using [sqlite3_finalize()] or until the same information is requested +** again in a different encoding. +** +** The names returned are the original un-aliased names of the +** database, table, and column. +** +** The first argument to the following calls is a [prepared statement]. +** These functions return information about the Nth column returned by +** the statement, where N is the second function argument. +** +** If the Nth column returned by the statement is an expression or +** subquery and is not a column value, then all of these functions return +** NULL. These routine might also return NULL if a memory allocation error +** occurs. Otherwise, they return the name of the attached database, table +** and column that query result column was extracted from. +** +** As with all other SQLite APIs, those postfixed with "16" return +** UTF-16 encoded strings, the other functions return UTF-8. {END} +** +** These APIs are only available if the library was compiled with the +** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol defined. +** +** {A13751} +** If two or more threads call one or more of these routines against the same +** prepared statement and column at the same time then the results are +** undefined. +** +** Requirements: +** [H13741] [H13742] [H13743] [H13744] [H13745] [H13746] [H13748] +** +** If two or more threads call one or more +** [sqlite3_column_database_name | column metadata interfaces] +** for the same [prepared statement] and result column +** at the same time then the results are undefined. +*/ +SQLITE_API const char *sqlite3_column_database_name(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_database_name16(sqlite3_stmt*,int); +SQLITE_API const char *sqlite3_column_table_name(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_table_name16(sqlite3_stmt*,int); +SQLITE_API const char *sqlite3_column_origin_name(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt*,int); + +/* +** CAPI3REF: Declared Datatype Of A Query Result {H13760} +** +** The first parameter is a [prepared statement]. +** If this statement is a [SELECT] statement and the Nth column of the +** returned result set of that [SELECT] is a table column (not an +** expression or subquery) then the declared type of the table +** column is returned. If the Nth column of the result set is an +** expression or subquery, then a NULL pointer is returned. +** The returned string is always UTF-8 encoded. {END} +** +** For example, given the database schema: +** +** CREATE TABLE t1(c1 VARIANT); +** +** and the following statement to be compiled: +** +** SELECT c1 + 1, c1 FROM t1; +** +** this routine would return the string "VARIANT" for the second result +** column (i==1), and a NULL pointer for the first result column (i==0). +** +** SQLite uses dynamic run-time typing. So just because a column +** is declared to contain a particular type does not mean that the +** data stored in that column is of the declared type. SQLite is +** strongly typed, but the typing is dynamic not static. Type +** is associated with individual values, not with the containers +** used to hold those values. +** +** Requirements: +** [H13761] [H13762] [H13763] +*/ +SQLITE_API const char *sqlite3_column_decltype(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int); + +/* +** CAPI3REF: Evaluate An SQL Statement {H13200} +** +** After a [prepared statement] has been prepared using either +** [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] or one of the legacy +** interfaces [sqlite3_prepare()] or [sqlite3_prepare16()], this function +** must be called one or more times to evaluate the statement. +** +** The details of the behavior of the sqlite3_step() interface depend +** on whether the statement was prepared using the newer "v2" interface +** [sqlite3_prepare_v2()] and [sqlite3_prepare16_v2()] or the older legacy +** interface [sqlite3_prepare()] and [sqlite3_prepare16()]. The use of the +** new "v2" interface is recommended for new applications but the legacy +** interface will continue to be supported. +** +** In the legacy interface, the return value will be either [SQLITE_BUSY], +** [SQLITE_DONE], [SQLITE_ROW], [SQLITE_ERROR], or [SQLITE_MISUSE]. +** With the "v2" interface, any of the other [result codes] or +** [extended result codes] might be returned as well. +** +** [SQLITE_BUSY] means that the database engine was unable to acquire the +** database locks it needs to do its job. If the statement is a [COMMIT] +** or occurs outside of an explicit transaction, then you can retry the +** statement. If the statement is not a [COMMIT] and occurs within a +** explicit transaction then you should rollback the transaction before +** continuing. +** +** [SQLITE_DONE] means that the statement has finished executing +** successfully. sqlite3_step() should not be called again on this virtual +** machine without first calling [sqlite3_reset()] to reset the virtual +** machine back to its initial state. +** +** If the SQL statement being executed returns any data, then [SQLITE_ROW] +** is returned each time a new row of data is ready for processing by the +** caller. The values may be accessed using the [column access functions]. +** sqlite3_step() is called again to retrieve the next row of data. +** +** [SQLITE_ERROR] means that a run-time error (such as a constraint +** violation) has occurred. sqlite3_step() should not be called again on +** the VM. More information may be found by calling [sqlite3_errmsg()]. +** With the legacy interface, a more specific error code (for example, +** [SQLITE_INTERRUPT], [SQLITE_SCHEMA], [SQLITE_CORRUPT], and so forth) +** can be obtained by calling [sqlite3_reset()] on the +** [prepared statement]. In the "v2" interface, +** the more specific error code is returned directly by sqlite3_step(). +** +** [SQLITE_MISUSE] means that the this routine was called inappropriately. +** Perhaps it was called on a [prepared statement] that has +** already been [sqlite3_finalize | finalized] or on one that had +** previously returned [SQLITE_ERROR] or [SQLITE_DONE]. Or it could +** be the case that the same database connection is being used by two or +** more threads at the same moment in time. +** +** Goofy Interface Alert: In the legacy interface, the sqlite3_step() +** API always returns a generic error code, [SQLITE_ERROR], following any +** error other than [SQLITE_BUSY] and [SQLITE_MISUSE]. You must call +** [sqlite3_reset()] or [sqlite3_finalize()] in order to find one of the +** specific [error codes] that better describes the error. +** We admit that this is a goofy design. The problem has been fixed +** with the "v2" interface. If you prepare all of your SQL statements +** using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] instead +** of the legacy [sqlite3_prepare()] and [sqlite3_prepare16()] interfaces, +** then the more specific [error codes] are returned directly +** by sqlite3_step(). The use of the "v2" interface is recommended. +** +** Requirements: +** [H13202] [H15304] [H15306] [H15308] [H15310] +*/ +SQLITE_API int sqlite3_step(sqlite3_stmt*); + +/* +** CAPI3REF: Number of columns in a result set {H13770} +** +** Returns the number of values in the current row of the result set. +** +** Requirements: +** [H13771] [H13772] +*/ +SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Fundamental Datatypes {H10265} +** KEYWORDS: SQLITE_TEXT +** +** {H10266} Every value in SQLite has one of five fundamental datatypes: +** +**
    +**
  • 64-bit signed integer +**
  • 64-bit IEEE floating point number +**
  • string +**
  • BLOB +**
  • NULL +**
{END} +** +** These constants are codes for each of those types. +** +** Note that the SQLITE_TEXT constant was also used in SQLite version 2 +** for a completely different meaning. Software that links against both +** SQLite version 2 and SQLite version 3 should use SQLITE3_TEXT, not +** SQLITE_TEXT. +*/ +#define SQLITE_INTEGER 1 +#define SQLITE_FLOAT 2 +#define SQLITE_BLOB 4 +#define SQLITE_NULL 5 +#ifdef SQLITE_TEXT +# undef SQLITE_TEXT +#else +# define SQLITE_TEXT 3 +#endif +#define SQLITE3_TEXT 3 + +/* +** CAPI3REF: Result Values From A Query {H13800} +** KEYWORDS: {column access functions} +** +** These routines form the "result set query" interface. +** +** These routines return information about a single column of the current +** result row of a query. In every case the first argument is a pointer +** to the [prepared statement] that is being evaluated (the [sqlite3_stmt*] +** that was returned from [sqlite3_prepare_v2()] or one of its variants) +** and the second argument is the index of the column for which information +** should be returned. The leftmost column of the result set has the index 0. +** +** If the SQL statement does not currently point to a valid row, or if the +** column index is out of range, the result is undefined. +** These routines may only be called when the most recent call to +** [sqlite3_step()] has returned [SQLITE_ROW] and neither +** [sqlite3_reset()] nor [sqlite3_finalize()] have been called subsequently. +** If any of these routines are called after [sqlite3_reset()] or +** [sqlite3_finalize()] or after [sqlite3_step()] has returned +** something other than [SQLITE_ROW], the results are undefined. +** If [sqlite3_step()] or [sqlite3_reset()] or [sqlite3_finalize()] +** are called from a different thread while any of these routines +** are pending, then the results are undefined. +** +** The sqlite3_column_type() routine returns the +** [SQLITE_INTEGER | datatype code] for the initial data type +** of the result column. The returned value is one of [SQLITE_INTEGER], +** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL]. The value +** returned by sqlite3_column_type() is only meaningful if no type +** conversions have occurred as described below. After a type conversion, +** the value returned by sqlite3_column_type() is undefined. Future +** versions of SQLite may change the behavior of sqlite3_column_type() +** following a type conversion. +** +** If the result is a BLOB or UTF-8 string then the sqlite3_column_bytes() +** routine returns the number of bytes in that BLOB or string. +** If the result is a UTF-16 string, then sqlite3_column_bytes() converts +** the string to UTF-8 and then returns the number of bytes. +** If the result is a numeric value then sqlite3_column_bytes() uses +** [sqlite3_snprintf()] to convert that value to a UTF-8 string and returns +** the number of bytes in that string. +** The value returned does not include the zero terminator at the end +** of the string. For clarity: the value returned is the number of +** bytes in the string, not the number of characters. +** +** Strings returned by sqlite3_column_text() and sqlite3_column_text16(), +** even empty strings, are always zero terminated. The return +** value from sqlite3_column_blob() for a zero-length BLOB is an arbitrary +** pointer, possibly even a NULL pointer. +** +** The sqlite3_column_bytes16() routine is similar to sqlite3_column_bytes() +** but leaves the result in UTF-16 in native byte order instead of UTF-8. +** The zero terminator is not included in this count. +** +** The object returned by [sqlite3_column_value()] is an +** [unprotected sqlite3_value] object. An unprotected sqlite3_value object +** may only be used with [sqlite3_bind_value()] and [sqlite3_result_value()]. +** If the [unprotected sqlite3_value] object returned by +** [sqlite3_column_value()] is used in any other way, including calls +** to routines like [sqlite3_value_int()], [sqlite3_value_text()], +** or [sqlite3_value_bytes()], then the behavior is undefined. +** +** These routines attempt to convert the value where appropriate. For +** example, if the internal representation is FLOAT and a text result +** is requested, [sqlite3_snprintf()] is used internally to perform the +** conversion automatically. The following table details the conversions +** that are applied: +** +**
+** +**
Internal
Type
Requested
Type
Conversion +** +**
NULL INTEGER Result is 0 +**
NULL FLOAT Result is 0.0 +**
NULL TEXT Result is NULL pointer +**
NULL BLOB Result is NULL pointer +**
INTEGER FLOAT Convert from integer to float +**
INTEGER TEXT ASCII rendering of the integer +**
INTEGER BLOB Same as INTEGER->TEXT +**
FLOAT INTEGER Convert from float to integer +**
FLOAT TEXT ASCII rendering of the float +**
FLOAT BLOB Same as FLOAT->TEXT +**
TEXT INTEGER Use atoi() +**
TEXT FLOAT Use atof() +**
TEXT BLOB No change +**
BLOB INTEGER Convert to TEXT then use atoi() +**
BLOB FLOAT Convert to TEXT then use atof() +**
BLOB TEXT Add a zero terminator if needed +**
+**
+** +** The table above makes reference to standard C library functions atoi() +** and atof(). SQLite does not really use these functions. It has its +** own equivalent internal routines. The atoi() and atof() names are +** used in the table for brevity and because they are familiar to most +** C programmers. +** +** Note that when type conversions occur, pointers returned by prior +** calls to sqlite3_column_blob(), sqlite3_column_text(), and/or +** sqlite3_column_text16() may be invalidated. +** Type conversions and pointer invalidations might occur +** in the following cases: +** +**
    +**
  • The initial content is a BLOB and sqlite3_column_text() or +** sqlite3_column_text16() is called. A zero-terminator might +** need to be added to the string.
  • +**
  • The initial content is UTF-8 text and sqlite3_column_bytes16() or +** sqlite3_column_text16() is called. The content must be converted +** to UTF-16.
  • +**
  • The initial content is UTF-16 text and sqlite3_column_bytes() or +** sqlite3_column_text() is called. The content must be converted +** to UTF-8.
  • +**
+** +** Conversions between UTF-16be and UTF-16le are always done in place and do +** not invalidate a prior pointer, though of course the content of the buffer +** that the prior pointer points to will have been modified. Other kinds +** of conversion are done in place when it is possible, but sometimes they +** are not possible and in those cases prior pointers are invalidated. +** +** The safest and easiest to remember policy is to invoke these routines +** in one of the following ways: +** +**
    +**
  • sqlite3_column_text() followed by sqlite3_column_bytes()
  • +**
  • sqlite3_column_blob() followed by sqlite3_column_bytes()
  • +**
  • sqlite3_column_text16() followed by sqlite3_column_bytes16()
  • +**
+** +** In other words, you should call sqlite3_column_text(), +** sqlite3_column_blob(), or sqlite3_column_text16() first to force the result +** into the desired format, then invoke sqlite3_column_bytes() or +** sqlite3_column_bytes16() to find the size of the result. Do not mix calls +** to sqlite3_column_text() or sqlite3_column_blob() with calls to +** sqlite3_column_bytes16(), and do not mix calls to sqlite3_column_text16() +** with calls to sqlite3_column_bytes(). +** +** The pointers returned are valid until a type conversion occurs as +** described above, or until [sqlite3_step()] or [sqlite3_reset()] or +** [sqlite3_finalize()] is called. The memory space used to hold strings +** and BLOBs is freed automatically. Do not pass the pointers returned +** [sqlite3_column_blob()], [sqlite3_column_text()], etc. into +** [sqlite3_free()]. +** +** If a memory allocation error occurs during the evaluation of any +** of these routines, a default value is returned. The default value +** is either the integer 0, the floating point number 0.0, or a NULL +** pointer. Subsequent calls to [sqlite3_errcode()] will return +** [SQLITE_NOMEM]. +** +** Requirements: +** [H13803] [H13806] [H13809] [H13812] [H13815] [H13818] [H13821] [H13824] +** [H13827] [H13830] +*/ +SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); +SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_int(sqlite3_stmt*, int iCol); +SQLITE_API sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol); +SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol); +SQLITE_API const void *sqlite3_column_text16(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol); +SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol); + +/* +** CAPI3REF: Destroy A Prepared Statement Object {H13300} +** +** The sqlite3_finalize() function is called to delete a [prepared statement]. +** If the statement was executed successfully or not executed at all, then +** SQLITE_OK is returned. If execution of the statement failed then an +** [error code] or [extended error code] is returned. +** +** This routine can be called at any point during the execution of the +** [prepared statement]. If the virtual machine has not +** completed execution when this routine is called, that is like +** encountering an error or an [sqlite3_interrupt | interrupt]. +** Incomplete updates may be rolled back and transactions canceled, +** depending on the circumstances, and the +** [error code] returned will be [SQLITE_ABORT]. +** +** Requirements: +** [H11302] [H11304] +*/ +SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Reset A Prepared Statement Object {H13330} +** +** The sqlite3_reset() function is called to reset a [prepared statement] +** object back to its initial state, ready to be re-executed. +** Any SQL statement variables that had values bound to them using +** the [sqlite3_bind_blob | sqlite3_bind_*() API] retain their values. +** Use [sqlite3_clear_bindings()] to reset the bindings. +** +** {H11332} The [sqlite3_reset(S)] interface resets the [prepared statement] S +** back to the beginning of its program. +** +** {H11334} If the most recent call to [sqlite3_step(S)] for the +** [prepared statement] S returned [SQLITE_ROW] or [SQLITE_DONE], +** or if [sqlite3_step(S)] has never before been called on S, +** then [sqlite3_reset(S)] returns [SQLITE_OK]. +** +** {H11336} If the most recent call to [sqlite3_step(S)] for the +** [prepared statement] S indicated an error, then +** [sqlite3_reset(S)] returns an appropriate [error code]. +** +** {H11338} The [sqlite3_reset(S)] interface does not change the values +** of any [sqlite3_bind_blob|bindings] on the [prepared statement] S. +*/ +SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Create Or Redefine SQL Functions {H16100} +** KEYWORDS: {function creation routines} +** KEYWORDS: {application-defined SQL function} +** KEYWORDS: {application-defined SQL functions} +** +** These two functions (collectively known as "function creation routines") +** are used to add SQL functions or aggregates or to redefine the behavior +** of existing SQL functions or aggregates. The only difference between the +** two is that the second parameter, the name of the (scalar) function or +** aggregate, is encoded in UTF-8 for sqlite3_create_function() and UTF-16 +** for sqlite3_create_function16(). +** +** The first parameter is the [database connection] to which the SQL +** function is to be added. If a single program uses more than one database +** connection internally, then SQL functions must be added individually to +** each database connection. +** +** The second parameter is the name of the SQL function to be created or +** redefined. The length of the name is limited to 255 bytes, exclusive of +** the zero-terminator. Note that the name length limit is in bytes, not +** characters. Any attempt to create a function with a longer name +** will result in [SQLITE_ERROR] being returned. +** +** The third parameter (nArg) +** is the number of arguments that the SQL function or +** aggregate takes. If this parameter is -1, then the SQL function or +** aggregate may take any number of arguments between 0 and the limit +** set by [sqlite3_limit]([SQLITE_LIMIT_FUNCTION_ARG]). If the third +** parameter is less than -1 or greater than 127 then the behavior is +** undefined. +** +** The fourth parameter, eTextRep, specifies what +** [SQLITE_UTF8 | text encoding] this SQL function prefers for +** its parameters. Any SQL function implementation should be able to work +** work with UTF-8, UTF-16le, or UTF-16be. But some implementations may be +** more efficient with one encoding than another. An application may +** invoke sqlite3_create_function() or sqlite3_create_function16() multiple +** times with the same function but with different values of eTextRep. +** When multiple implementations of the same function are available, SQLite +** will pick the one that involves the least amount of data conversion. +** If there is only a single implementation which does not care what text +** encoding is used, then the fourth argument should be [SQLITE_ANY]. +** +** The fifth parameter is an arbitrary pointer. The implementation of the +** function can gain access to this pointer using [sqlite3_user_data()]. +** +** The seventh, eighth and ninth parameters, xFunc, xStep and xFinal, are +** pointers to C-language functions that implement the SQL function or +** aggregate. A scalar SQL function requires an implementation of the xFunc +** callback only, NULL pointers should be passed as the xStep and xFinal +** parameters. An aggregate SQL function requires an implementation of xStep +** and xFinal and NULL should be passed for xFunc. To delete an existing +** SQL function or aggregate, pass NULL for all three function callbacks. +** +** It is permitted to register multiple implementations of the same +** functions with the same name but with either differing numbers of +** arguments or differing preferred text encodings. SQLite will use +** the implementation that most closely matches the way in which the +** SQL function is used. A function implementation with a non-negative +** nArg parameter is a better match than a function implementation with +** a negative nArg. A function where the preferred text encoding +** matches the database encoding is a better +** match than a function where the encoding is different. +** A function where the encoding difference is between UTF16le and UTF16be +** is a closer match than a function where the encoding difference is +** between UTF8 and UTF16. +** +** Built-in functions may be overloaded by new application-defined functions. +** The first application-defined function with a given name overrides all +** built-in functions in the same [database connection] with the same name. +** Subsequent application-defined functions of the same name only override +** prior application-defined functions that are an exact match for the +** number of parameters and preferred encoding. +** +** An application-defined function is permitted to call other +** SQLite interfaces. However, such calls must not +** close the database connection nor finalize or reset the prepared +** statement in which the function is running. +** +** Requirements: +** [H16103] [H16106] [H16109] [H16112] [H16118] [H16121] [H16127] +** [H16130] [H16133] [H16136] [H16139] [H16142] +*/ +SQLITE_API int sqlite3_create_function( + sqlite3 *db, + const char *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*) +); +SQLITE_API int sqlite3_create_function16( + sqlite3 *db, + const void *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*) +); + +/* +** CAPI3REF: Text Encodings {H10267} +** +** These constant define integer codes that represent the various +** text encodings supported by SQLite. +*/ +#define SQLITE_UTF8 1 +#define SQLITE_UTF16LE 2 +#define SQLITE_UTF16BE 3 +#define SQLITE_UTF16 4 /* Use native byte order */ +#define SQLITE_ANY 5 /* sqlite3_create_function only */ +#define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */ + +/* +** CAPI3REF: Deprecated Functions +** DEPRECATED +** +** These functions are [deprecated]. In order to maintain +** backwards compatibility with older code, these functions continue +** to be supported. However, new applications should avoid +** the use of these functions. To help encourage people to avoid +** using these functions, we are not going to tell you what they do. +*/ +#ifndef SQLITE_OMIT_DEPRECATED +SQLITE_API SQLITE_DEPRECATED int sqlite3_aggregate_count(sqlite3_context*); +SQLITE_API SQLITE_DEPRECATED int sqlite3_expired(sqlite3_stmt*); +SQLITE_API SQLITE_DEPRECATED int sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*); +SQLITE_API SQLITE_DEPRECATED int sqlite3_global_recover(void); +SQLITE_API SQLITE_DEPRECATED void sqlite3_thread_cleanup(void); +SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int),void*,sqlite3_int64); +#endif + +/* +** CAPI3REF: Obtaining SQL Function Parameter Values {H15100} +** +** The C-language implementation of SQL functions and aggregates uses +** this set of interface routines to access the parameter values on +** the function or aggregate. +** +** The xFunc (for scalar functions) or xStep (for aggregates) parameters +** to [sqlite3_create_function()] and [sqlite3_create_function16()] +** define callbacks that implement the SQL functions and aggregates. +** The 4th parameter to these callbacks is an array of pointers to +** [protected sqlite3_value] objects. There is one [sqlite3_value] object for +** each parameter to the SQL function. These routines are used to +** extract values from the [sqlite3_value] objects. +** +** These routines work only with [protected sqlite3_value] objects. +** Any attempt to use these routines on an [unprotected sqlite3_value] +** object results in undefined behavior. +** +** These routines work just like the corresponding [column access functions] +** except that these routines take a single [protected sqlite3_value] object +** pointer instead of a [sqlite3_stmt*] pointer and an integer column number. +** +** The sqlite3_value_text16() interface extracts a UTF-16 string +** in the native byte-order of the host machine. The +** sqlite3_value_text16be() and sqlite3_value_text16le() interfaces +** extract UTF-16 strings as big-endian and little-endian respectively. +** +** The sqlite3_value_numeric_type() interface attempts to apply +** numeric affinity to the value. This means that an attempt is +** made to convert the value to an integer or floating point. If +** such a conversion is possible without loss of information (in other +** words, if the value is a string that looks like a number) +** then the conversion is performed. Otherwise no conversion occurs. +** The [SQLITE_INTEGER | datatype] after conversion is returned. +** +** Please pay particular attention to the fact that the pointer returned +** from [sqlite3_value_blob()], [sqlite3_value_text()], or +** [sqlite3_value_text16()] can be invalidated by a subsequent call to +** [sqlite3_value_bytes()], [sqlite3_value_bytes16()], [sqlite3_value_text()], +** or [sqlite3_value_text16()]. +** +** These routines must be called from the same thread as +** the SQL function that supplied the [sqlite3_value*] parameters. +** +** Requirements: +** [H15103] [H15106] [H15109] [H15112] [H15115] [H15118] [H15121] [H15124] +** [H15127] [H15130] [H15133] [H15136] +*/ +SQLITE_API const void *sqlite3_value_blob(sqlite3_value*); +SQLITE_API int sqlite3_value_bytes(sqlite3_value*); +SQLITE_API int sqlite3_value_bytes16(sqlite3_value*); +SQLITE_API double sqlite3_value_double(sqlite3_value*); +SQLITE_API int sqlite3_value_int(sqlite3_value*); +SQLITE_API sqlite3_int64 sqlite3_value_int64(sqlite3_value*); +SQLITE_API const unsigned char *sqlite3_value_text(sqlite3_value*); +SQLITE_API const void *sqlite3_value_text16(sqlite3_value*); +SQLITE_API const void *sqlite3_value_text16le(sqlite3_value*); +SQLITE_API const void *sqlite3_value_text16be(sqlite3_value*); +SQLITE_API int sqlite3_value_type(sqlite3_value*); +SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*); + +/* +** CAPI3REF: Obtain Aggregate Function Context {H16210} +** +** The implementation of aggregate SQL functions use this routine to allocate +** a structure for storing their state. +** +** The first time the sqlite3_aggregate_context() routine is called for a +** particular aggregate, SQLite allocates nBytes of memory, zeroes out that +** memory, and returns a pointer to it. On second and subsequent calls to +** sqlite3_aggregate_context() for the same aggregate function index, +** the same buffer is returned. The implementation of the aggregate can use +** the returned buffer to accumulate data. +** +** SQLite automatically frees the allocated buffer when the aggregate +** query concludes. +** +** The first parameter should be a copy of the +** [sqlite3_context | SQL function context] that is the first parameter +** to the callback routine that implements the aggregate function. +** +** This routine must be called from the same thread in which +** the aggregate SQL function is running. +** +** Requirements: +** [H16211] [H16213] [H16215] [H16217] +*/ +SQLITE_API void *sqlite3_aggregate_context(sqlite3_context*, int nBytes); + +/* +** CAPI3REF: User Data For Functions {H16240} +** +** The sqlite3_user_data() interface returns a copy of +** the pointer that was the pUserData parameter (the 5th parameter) +** of the [sqlite3_create_function()] +** and [sqlite3_create_function16()] routines that originally +** registered the application defined function. {END} +** +** This routine must be called from the same thread in which +** the application-defined function is running. +** +** Requirements: +** [H16243] +*/ +SQLITE_API void *sqlite3_user_data(sqlite3_context*); + +/* +** CAPI3REF: Database Connection For Functions {H16250} +** +** The sqlite3_context_db_handle() interface returns a copy of +** the pointer to the [database connection] (the 1st parameter) +** of the [sqlite3_create_function()] +** and [sqlite3_create_function16()] routines that originally +** registered the application defined function. +** +** Requirements: +** [H16253] +*/ +SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*); + +/* +** CAPI3REF: Function Auxiliary Data {H16270} +** +** The following two functions may be used by scalar SQL functions to +** associate metadata with argument values. If the same value is passed to +** multiple invocations of the same SQL function during query execution, under +** some circumstances the associated metadata may be preserved. This may +** be used, for example, to add a regular-expression matching scalar +** function. The compiled version of the regular expression is stored as +** metadata associated with the SQL value passed as the regular expression +** pattern. The compiled regular expression can be reused on multiple +** invocations of the same function so that the original pattern string +** does not need to be recompiled on each invocation. +** +** The sqlite3_get_auxdata() interface returns a pointer to the metadata +** associated by the sqlite3_set_auxdata() function with the Nth argument +** value to the application-defined function. If no metadata has been ever +** been set for the Nth argument of the function, or if the corresponding +** function parameter has changed since the meta-data was set, +** then sqlite3_get_auxdata() returns a NULL pointer. +** +** The sqlite3_set_auxdata() interface saves the metadata +** pointed to by its 3rd parameter as the metadata for the N-th +** argument of the application-defined function. Subsequent +** calls to sqlite3_get_auxdata() might return this data, if it has +** not been destroyed. +** If it is not NULL, SQLite will invoke the destructor +** function given by the 4th parameter to sqlite3_set_auxdata() on +** the metadata when the corresponding function parameter changes +** or when the SQL statement completes, whichever comes first. +** +** SQLite is free to call the destructor and drop metadata on any +** parameter of any function at any time. The only guarantee is that +** the destructor will be called before the metadata is dropped. +** +** In practice, metadata is preserved between function calls for +** expressions that are constant at compile time. This includes literal +** values and SQL variables. +** +** These routines must be called from the same thread in which +** the SQL function is running. +** +** Requirements: +** [H16272] [H16274] [H16276] [H16277] [H16278] [H16279] +*/ +SQLITE_API void *sqlite3_get_auxdata(sqlite3_context*, int N); +SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*)); + + +/* +** CAPI3REF: Constants Defining Special Destructor Behavior {H10280} +** +** These are special values for the destructor that is passed in as the +** final argument to routines like [sqlite3_result_blob()]. If the destructor +** argument is SQLITE_STATIC, it means that the content pointer is constant +** and will never change. It does not need to be destroyed. The +** SQLITE_TRANSIENT value means that the content will likely change in +** the near future and that SQLite should make its own private copy of +** the content before returning. +** +** The typedef is necessary to work around problems in certain +** C++ compilers. See ticket #2191. +*/ +typedef void (*sqlite3_destructor_type)(void*); +#define SQLITE_STATIC ((sqlite3_destructor_type)0) +#define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1) + +/* +** CAPI3REF: Setting The Result Of An SQL Function {H16400} +** +** These routines are used by the xFunc or xFinal callbacks that +** implement SQL functions and aggregates. See +** [sqlite3_create_function()] and [sqlite3_create_function16()] +** for additional information. +** +** These functions work very much like the [parameter binding] family of +** functions used to bind values to host parameters in prepared statements. +** Refer to the [SQL parameter] documentation for additional information. +** +** The sqlite3_result_blob() interface sets the result from +** an application-defined function to be the BLOB whose content is pointed +** to by the second parameter and which is N bytes long where N is the +** third parameter. +** +** The sqlite3_result_zeroblob() interfaces set the result of +** the application-defined function to be a BLOB containing all zero +** bytes and N bytes in size, where N is the value of the 2nd parameter. +** +** The sqlite3_result_double() interface sets the result from +** an application-defined function to be a floating point value specified +** by its 2nd argument. +** +** The sqlite3_result_error() and sqlite3_result_error16() functions +** cause the implemented SQL function to throw an exception. +** SQLite uses the string pointed to by the +** 2nd parameter of sqlite3_result_error() or sqlite3_result_error16() +** as the text of an error message. SQLite interprets the error +** message string from sqlite3_result_error() as UTF-8. SQLite +** interprets the string from sqlite3_result_error16() as UTF-16 in native +** byte order. If the third parameter to sqlite3_result_error() +** or sqlite3_result_error16() is negative then SQLite takes as the error +** message all text up through the first zero character. +** If the third parameter to sqlite3_result_error() or +** sqlite3_result_error16() is non-negative then SQLite takes that many +** bytes (not characters) from the 2nd parameter as the error message. +** The sqlite3_result_error() and sqlite3_result_error16() +** routines make a private copy of the error message text before +** they return. Hence, the calling function can deallocate or +** modify the text after they return without harm. +** The sqlite3_result_error_code() function changes the error code +** returned by SQLite as a result of an error in a function. By default, +** the error code is SQLITE_ERROR. A subsequent call to sqlite3_result_error() +** or sqlite3_result_error16() resets the error code to SQLITE_ERROR. +** +** The sqlite3_result_toobig() interface causes SQLite to throw an error +** indicating that a string or BLOB is to long to represent. +** +** The sqlite3_result_nomem() interface causes SQLite to throw an error +** indicating that a memory allocation failed. +** +** The sqlite3_result_int() interface sets the return value +** of the application-defined function to be the 32-bit signed integer +** value given in the 2nd argument. +** The sqlite3_result_int64() interface sets the return value +** of the application-defined function to be the 64-bit signed integer +** value given in the 2nd argument. +** +** The sqlite3_result_null() interface sets the return value +** of the application-defined function to be NULL. +** +** The sqlite3_result_text(), sqlite3_result_text16(), +** sqlite3_result_text16le(), and sqlite3_result_text16be() interfaces +** set the return value of the application-defined function to be +** a text string which is represented as UTF-8, UTF-16 native byte order, +** UTF-16 little endian, or UTF-16 big endian, respectively. +** SQLite takes the text result from the application from +** the 2nd parameter of the sqlite3_result_text* interfaces. +** If the 3rd parameter to the sqlite3_result_text* interfaces +** is negative, then SQLite takes result text from the 2nd parameter +** through the first zero character. +** If the 3rd parameter to the sqlite3_result_text* interfaces +** is non-negative, then as many bytes (not characters) of the text +** pointed to by the 2nd parameter are taken as the application-defined +** function result. +** If the 4th parameter to the sqlite3_result_text* interfaces +** or sqlite3_result_blob is a non-NULL pointer, then SQLite calls that +** function as the destructor on the text or BLOB result when it has +** finished using that result. +** If the 4th parameter to the sqlite3_result_text* interfaces or to +** sqlite3_result_blob is the special constant SQLITE_STATIC, then SQLite +** assumes that the text or BLOB result is in constant space and does not +** copy the content of the parameter nor call a destructor on the content +** when it has finished using that result. +** If the 4th parameter to the sqlite3_result_text* interfaces +** or sqlite3_result_blob is the special constant SQLITE_TRANSIENT +** then SQLite makes a copy of the result into space obtained from +** from [sqlite3_malloc()] before it returns. +** +** The sqlite3_result_value() interface sets the result of +** the application-defined function to be a copy the +** [unprotected sqlite3_value] object specified by the 2nd parameter. The +** sqlite3_result_value() interface makes a copy of the [sqlite3_value] +** so that the [sqlite3_value] specified in the parameter may change or +** be deallocated after sqlite3_result_value() returns without harm. +** A [protected sqlite3_value] object may always be used where an +** [unprotected sqlite3_value] object is required, so either +** kind of [sqlite3_value] object can be used with this interface. +** +** If these routines are called from within the different thread +** than the one containing the application-defined function that received +** the [sqlite3_context] pointer, the results are undefined. +** +** Requirements: +** [H16403] [H16406] [H16409] [H16412] [H16415] [H16418] [H16421] [H16424] +** [H16427] [H16430] [H16433] [H16436] [H16439] [H16442] [H16445] [H16448] +** [H16451] [H16454] [H16457] [H16460] [H16463] +*/ +SQLITE_API void sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*)); +SQLITE_API void sqlite3_result_double(sqlite3_context*, double); +SQLITE_API void sqlite3_result_error(sqlite3_context*, const char*, int); +SQLITE_API void sqlite3_result_error16(sqlite3_context*, const void*, int); +SQLITE_API void sqlite3_result_error_toobig(sqlite3_context*); +SQLITE_API void sqlite3_result_error_nomem(sqlite3_context*); +SQLITE_API void sqlite3_result_error_code(sqlite3_context*, int); +SQLITE_API void sqlite3_result_int(sqlite3_context*, int); +SQLITE_API void sqlite3_result_int64(sqlite3_context*, sqlite3_int64); +SQLITE_API void sqlite3_result_null(sqlite3_context*); +SQLITE_API void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*)); +SQLITE_API void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*)); +SQLITE_API void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*)); +SQLITE_API void sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*)); +SQLITE_API void sqlite3_result_value(sqlite3_context*, sqlite3_value*); +SQLITE_API void sqlite3_result_zeroblob(sqlite3_context*, int n); + +/* +** CAPI3REF: Define New Collating Sequences {H16600} +** +** These functions are used to add new collation sequences to the +** [database connection] specified as the first argument. +** +** The name of the new collation sequence is specified as a UTF-8 string +** for sqlite3_create_collation() and sqlite3_create_collation_v2() +** and a UTF-16 string for sqlite3_create_collation16(). In all cases +** the name is passed as the second function argument. +** +** The third argument may be one of the constants [SQLITE_UTF8], +** [SQLITE_UTF16LE], or [SQLITE_UTF16BE], indicating that the user-supplied +** routine expects to be passed pointers to strings encoded using UTF-8, +** UTF-16 little-endian, or UTF-16 big-endian, respectively. The +** third argument might also be [SQLITE_UTF16] to indicate that the routine +** expects pointers to be UTF-16 strings in the native byte order, or the +** argument can be [SQLITE_UTF16_ALIGNED] if the +** the routine expects pointers to 16-bit word aligned strings +** of UTF-16 in the native byte order. +** +** A pointer to the user supplied routine must be passed as the fifth +** argument. If it is NULL, this is the same as deleting the collation +** sequence (so that SQLite cannot call it anymore). +** Each time the application supplied function is invoked, it is passed +** as its first parameter a copy of the void* passed as the fourth argument +** to sqlite3_create_collation() or sqlite3_create_collation16(). +** +** The remaining arguments to the application-supplied routine are two strings, +** each represented by a (length, data) pair and encoded in the encoding +** that was passed as the third argument when the collation sequence was +** registered. {END} The application defined collation routine should +** return negative, zero or positive if the first string is less than, +** equal to, or greater than the second string. i.e. (STRING1 - STRING2). +** +** The sqlite3_create_collation_v2() works like sqlite3_create_collation() +** except that it takes an extra argument which is a destructor for +** the collation. The destructor is called when the collation is +** destroyed and is passed a copy of the fourth parameter void* pointer +** of the sqlite3_create_collation_v2(). +** Collations are destroyed when they are overridden by later calls to the +** collation creation functions or when the [database connection] is closed +** using [sqlite3_close()]. +** +** See also: [sqlite3_collation_needed()] and [sqlite3_collation_needed16()]. +** +** Requirements: +** [H16603] [H16604] [H16606] [H16609] [H16612] [H16615] [H16618] [H16621] +** [H16624] [H16627] [H16630] +*/ +SQLITE_API int sqlite3_create_collation( + sqlite3*, + const char *zName, + int eTextRep, + void*, + int(*xCompare)(void*,int,const void*,int,const void*) +); +SQLITE_API int sqlite3_create_collation_v2( + sqlite3*, + const char *zName, + int eTextRep, + void*, + int(*xCompare)(void*,int,const void*,int,const void*), + void(*xDestroy)(void*) +); +SQLITE_API int sqlite3_create_collation16( + sqlite3*, + const void *zName, + int eTextRep, + void*, + int(*xCompare)(void*,int,const void*,int,const void*) +); + +/* +** CAPI3REF: Collation Needed Callbacks {H16700} +** +** To avoid having to register all collation sequences before a database +** can be used, a single callback function may be registered with the +** [database connection] to be called whenever an undefined collation +** sequence is required. +** +** If the function is registered using the sqlite3_collation_needed() API, +** then it is passed the names of undefined collation sequences as strings +** encoded in UTF-8. {H16703} If sqlite3_collation_needed16() is used, +** the names are passed as UTF-16 in machine native byte order. +** A call to either function replaces any existing callback. +** +** When the callback is invoked, the first argument passed is a copy +** of the second argument to sqlite3_collation_needed() or +** sqlite3_collation_needed16(). The second argument is the database +** connection. The third argument is one of [SQLITE_UTF8], [SQLITE_UTF16BE], +** or [SQLITE_UTF16LE], indicating the most desirable form of the collation +** sequence function required. The fourth parameter is the name of the +** required collation sequence. +** +** The callback function should register the desired collation using +** [sqlite3_create_collation()], [sqlite3_create_collation16()], or +** [sqlite3_create_collation_v2()]. +** +** Requirements: +** [H16702] [H16704] [H16706] +*/ +SQLITE_API int sqlite3_collation_needed( + sqlite3*, + void*, + void(*)(void*,sqlite3*,int eTextRep,const char*) +); +SQLITE_API int sqlite3_collation_needed16( + sqlite3*, + void*, + void(*)(void*,sqlite3*,int eTextRep,const void*) +); + +/* +** Specify the key for an encrypted database. This routine should be +** called right after sqlite3_open(). +** +** The code to implement this API is not available in the public release +** of SQLite. +*/ +SQLITE_API int sqlite3_key( + sqlite3 *db, /* Database to be rekeyed */ + const void *pKey, int nKey /* The key */ +); + +/* +** Change the key on an open database. If the current database is not +** encrypted, this routine will encrypt it. If pNew==0 or nNew==0, the +** database is decrypted. +** +** The code to implement this API is not available in the public release +** of SQLite. +*/ +SQLITE_API int sqlite3_rekey( + sqlite3 *db, /* Database to be rekeyed */ + const void *pKey, int nKey /* The new key */ +); + +/* +** CAPI3REF: Suspend Execution For A Short Time {H10530} +** +** The sqlite3_sleep() function causes the current thread to suspend execution +** for at least a number of milliseconds specified in its parameter. +** +** If the operating system does not support sleep requests with +** millisecond time resolution, then the time will be rounded up to +** the nearest second. The number of milliseconds of sleep actually +** requested from the operating system is returned. +** +** SQLite implements this interface by calling the xSleep() +** method of the default [sqlite3_vfs] object. +** +** Requirements: [H10533] [H10536] +*/ +SQLITE_API int sqlite3_sleep(int); + +/* +** CAPI3REF: Name Of The Folder Holding Temporary Files {H10310} +** +** If this global variable is made to point to a string which is +** the name of a folder (a.k.a. directory), then all temporary files +** created by SQLite will be placed in that directory. If this variable +** is a NULL pointer, then SQLite performs a search for an appropriate +** temporary file directory. +** +** It is not safe to read or modify this variable in more than one +** thread at a time. It is not safe to read or modify this variable +** if a [database connection] is being used at the same time in a separate +** thread. +** It is intended that this variable be set once +** as part of process initialization and before any SQLite interface +** routines have been called and that this variable remain unchanged +** thereafter. +** +** The [temp_store_directory pragma] may modify this variable and cause +** it to point to memory obtained from [sqlite3_malloc]. Furthermore, +** the [temp_store_directory pragma] always assumes that any string +** that this variable points to is held in memory obtained from +** [sqlite3_malloc] and the pragma may attempt to free that memory +** using [sqlite3_free]. +** Hence, if this variable is modified directly, either it should be +** made NULL or made to point to memory obtained from [sqlite3_malloc] +** or else the use of the [temp_store_directory pragma] should be avoided. +*/ +SQLITE_API SQLITE_EXTERN char *sqlite3_temp_directory; + +/* +** CAPI3REF: Test For Auto-Commit Mode {H12930} +** KEYWORDS: {autocommit mode} +** +** The sqlite3_get_autocommit() interface returns non-zero or +** zero if the given database connection is or is not in autocommit mode, +** respectively. Autocommit mode is on by default. +** Autocommit mode is disabled by a [BEGIN] statement. +** Autocommit mode is re-enabled by a [COMMIT] or [ROLLBACK]. +** +** If certain kinds of errors occur on a statement within a multi-statement +** transaction (errors including [SQLITE_FULL], [SQLITE_IOERR], +** [SQLITE_NOMEM], [SQLITE_BUSY], and [SQLITE_INTERRUPT]) then the +** transaction might be rolled back automatically. The only way to +** find out whether SQLite automatically rolled back the transaction after +** an error is to use this function. +** +** If another thread changes the autocommit status of the database +** connection while this routine is running, then the return value +** is undefined. +** +** Requirements: [H12931] [H12932] [H12933] [H12934] +*/ +SQLITE_API int sqlite3_get_autocommit(sqlite3*); + +/* +** CAPI3REF: Find The Database Handle Of A Prepared Statement {H13120} +** +** The sqlite3_db_handle interface returns the [database connection] handle +** to which a [prepared statement] belongs. The [database connection] +** returned by sqlite3_db_handle is the same [database connection] that was the first argument +** to the [sqlite3_prepare_v2()] call (or its variants) that was used to +** create the statement in the first place. +** +** Requirements: [H13123] +*/ +SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); + +/* +** CAPI3REF: Find the next prepared statement {H13140} +** +** This interface returns a pointer to the next [prepared statement] after +** pStmt associated with the [database connection] pDb. If pStmt is NULL +** then this interface returns a pointer to the first prepared statement +** associated with the database connection pDb. If no prepared statement +** satisfies the conditions of this routine, it returns NULL. +** +** The [database connection] pointer D in a call to +** [sqlite3_next_stmt(D,S)] must refer to an open database +** connection and in particular must not be a NULL pointer. +** +** Requirements: [H13143] [H13146] [H13149] [H13152] +*/ +SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Commit And Rollback Notification Callbacks {H12950} +** +** The sqlite3_commit_hook() interface registers a callback +** function to be invoked whenever a transaction is [COMMIT | committed]. +** Any callback set by a previous call to sqlite3_commit_hook() +** for the same database connection is overridden. +** The sqlite3_rollback_hook() interface registers a callback +** function to be invoked whenever a transaction is [ROLLBACK | rolled back]. +** Any callback set by a previous call to sqlite3_commit_hook() +** for the same database connection is overridden. +** The pArg argument is passed through to the callback. +** If the callback on a commit hook function returns non-zero, +** then the commit is converted into a rollback. +** +** If another function was previously registered, its +** pArg value is returned. Otherwise NULL is returned. +** +** The callback implementation must not do anything that will modify +** the database connection that invoked the callback. Any actions +** to modify the database connection must be deferred until after the +** completion of the [sqlite3_step()] call that triggered the commit +** or rollback hook in the first place. +** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their +** database connections for the meaning of "modify" in this paragraph. +** +** Registering a NULL function disables the callback. +** +** When the commit hook callback routine returns zero, the [COMMIT] +** operation is allowed to continue normally. If the commit hook +** returns non-zero, then the [COMMIT] is converted into a [ROLLBACK]. +** The rollback hook is invoked on a rollback that results from a commit +** hook returning non-zero, just as it would be with any other rollback. +** +** For the purposes of this API, a transaction is said to have been +** rolled back if an explicit "ROLLBACK" statement is executed, or +** an error or constraint causes an implicit rollback to occur. +** The rollback callback is not invoked if a transaction is +** automatically rolled back because the database connection is closed. +** The rollback callback is not invoked if a transaction is +** rolled back because a commit callback returned non-zero. +** Check on this +** +** See also the [sqlite3_update_hook()] interface. +** +** Requirements: +** [H12951] [H12952] [H12953] [H12954] [H12955] +** [H12961] [H12962] [H12963] [H12964] +*/ +SQLITE_API void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); +SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); + +/* +** CAPI3REF: Data Change Notification Callbacks {H12970} +** +** The sqlite3_update_hook() interface registers a callback function +** with the [database connection] identified by the first argument +** to be invoked whenever a row is updated, inserted or deleted. +** Any callback set by a previous call to this function +** for the same database connection is overridden. +** +** The second argument is a pointer to the function to invoke when a +** row is updated, inserted or deleted. +** The first argument to the callback is a copy of the third argument +** to sqlite3_update_hook(). +** The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE], +** or [SQLITE_UPDATE], depending on the operation that caused the callback +** to be invoked. +** The third and fourth arguments to the callback contain pointers to the +** database and table name containing the affected row. +** The final callback parameter is the [rowid] of the row. +** In the case of an update, this is the [rowid] after the update takes place. +** +** The update hook is not invoked when internal system tables are +** modified (i.e. sqlite_master and sqlite_sequence). +** +** In the current implementation, the update hook +** is not invoked when duplication rows are deleted because of an +** [ON CONFLICT | ON CONFLICT REPLACE] clause. Nor is the update hook +** invoked when rows are deleted using the [truncate optimization]. +** The exceptions defined in this paragraph might change in a future +** release of SQLite. +** +** The update hook implementation must not do anything that will modify +** the database connection that invoked the update hook. Any actions +** to modify the database connection must be deferred until after the +** completion of the [sqlite3_step()] call that triggered the update hook. +** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their +** database connections for the meaning of "modify" in this paragraph. +** +** If another function was previously registered, its pArg value +** is returned. Otherwise NULL is returned. +** +** See also the [sqlite3_commit_hook()] and [sqlite3_rollback_hook()] +** interfaces. +** +** Requirements: +** [H12971] [H12973] [H12975] [H12977] [H12979] [H12981] [H12983] [H12986] +*/ +SQLITE_API void *sqlite3_update_hook( + sqlite3*, + void(*)(void *,int ,char const *,char const *,sqlite3_int64), + void* +); + +/* +** CAPI3REF: Enable Or Disable Shared Pager Cache {H10330} +** KEYWORDS: {shared cache} +** +** This routine enables or disables the sharing of the database cache +** and schema data structures between [database connection | connections] +** to the same database. Sharing is enabled if the argument is true +** and disabled if the argument is false. +** +** Cache sharing is enabled and disabled for an entire process. +** This is a change as of SQLite version 3.5.0. In prior versions of SQLite, +** sharing was enabled or disabled for each thread separately. +** +** The cache sharing mode set by this interface effects all subsequent +** calls to [sqlite3_open()], [sqlite3_open_v2()], and [sqlite3_open16()]. +** Existing database connections continue use the sharing mode +** that was in effect at the time they were opened. +** +** Virtual tables cannot be used with a shared cache. When shared +** cache is enabled, the [sqlite3_create_module()] API used to register +** virtual tables will always return an error. +** +** This routine returns [SQLITE_OK] if shared cache was enabled or disabled +** successfully. An [error code] is returned otherwise. +** +** Shared cache is disabled by default. But this might change in +** future releases of SQLite. Applications that care about shared +** cache setting should set it explicitly. +** +** See Also: [SQLite Shared-Cache Mode] +** +** Requirements: [H10331] [H10336] [H10337] [H10339] +*/ +SQLITE_API int sqlite3_enable_shared_cache(int); + +/* +** CAPI3REF: Attempt To Free Heap Memory {H17340} +** +** The sqlite3_release_memory() interface attempts to free N bytes +** of heap memory by deallocating non-essential memory allocations +** held by the database library. {END} Memory used to cache database +** pages to improve performance is an example of non-essential memory. +** sqlite3_release_memory() returns the number of bytes actually freed, +** which might be more or less than the amount requested. +** +** Requirements: [H17341] [H17342] +*/ +SQLITE_API int sqlite3_release_memory(int); + +/* +** CAPI3REF: Impose A Limit On Heap Size {H17350} +** +** The sqlite3_soft_heap_limit() interface places a "soft" limit +** on the amount of heap memory that may be allocated by SQLite. +** If an internal allocation is requested that would exceed the +** soft heap limit, [sqlite3_release_memory()] is invoked one or +** more times to free up some space before the allocation is performed. +** +** The limit is called "soft", because if [sqlite3_release_memory()] +** cannot free sufficient memory to prevent the limit from being exceeded, +** the memory is allocated anyway and the current operation proceeds. +** +** A negative or zero value for N means that there is no soft heap limit and +** [sqlite3_release_memory()] will only be called when memory is exhausted. +** The default value for the soft heap limit is zero. +** +** SQLite makes a best effort to honor the soft heap limit. +** But if the soft heap limit cannot be honored, execution will +** continue without error or notification. This is why the limit is +** called a "soft" limit. It is advisory only. +** +** Prior to SQLite version 3.5.0, this routine only constrained the memory +** allocated by a single thread - the same thread in which this routine +** runs. Beginning with SQLite version 3.5.0, the soft heap limit is +** applied to all threads. The value specified for the soft heap limit +** is an upper bound on the total memory allocation for all threads. In +** version 3.5.0 there is no mechanism for limiting the heap usage for +** individual threads. +** +** Requirements: +** [H16351] [H16352] [H16353] [H16354] [H16355] [H16358] +*/ +SQLITE_API void sqlite3_soft_heap_limit(int); + +/* +** CAPI3REF: Extract Metadata About A Column Of A Table {H12850} +** +** This routine returns metadata about a specific column of a specific +** database table accessible using the [database connection] handle +** passed as the first function argument. +** +** The column is identified by the second, third and fourth parameters to +** this function. The second parameter is either the name of the database +** (i.e. "main", "temp" or an attached database) containing the specified +** table or NULL. If it is NULL, then all attached databases are searched +** for the table using the same algorithm used by the database engine to +** resolve unqualified table references. +** +** The third and fourth parameters to this function are the table and column +** name of the desired column, respectively. Neither of these parameters +** may be NULL. +** +** Metadata is returned by writing to the memory locations passed as the 5th +** and subsequent parameters to this function. Any of these arguments may be +** NULL, in which case the corresponding element of metadata is omitted. +** +**
+** +**
Parameter Output
Type
Description +** +**
5th const char* Data type +**
6th const char* Name of default collation sequence +**
7th int True if column has a NOT NULL constraint +**
8th int True if column is part of the PRIMARY KEY +**
9th int True if column is [AUTOINCREMENT] +**
+**
+** +** The memory pointed to by the character pointers returned for the +** declaration type and collation sequence is valid only until the next +** call to any SQLite API function. +** +** If the specified table is actually a view, an [error code] is returned. +** +** If the specified column is "rowid", "oid" or "_rowid_" and an +** [INTEGER PRIMARY KEY] column has been explicitly declared, then the output +** parameters are set for the explicitly declared column. If there is no +** explicitly declared [INTEGER PRIMARY KEY] column, then the output +** parameters are set as follows: +** +**
+**     data type: "INTEGER"
+**     collation sequence: "BINARY"
+**     not null: 0
+**     primary key: 1
+**     auto increment: 0
+** 
+** +** This function may load one or more schemas from database files. If an +** error occurs during this process, or if the requested table or column +** cannot be found, an [error code] is returned and an error message left +** in the [database connection] (to be retrieved using sqlite3_errmsg()). +** +** This API is only available if the library was compiled with the +** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol defined. +*/ +SQLITE_API int sqlite3_table_column_metadata( + sqlite3 *db, /* Connection handle */ + const char *zDbName, /* Database name or NULL */ + const char *zTableName, /* Table name */ + const char *zColumnName, /* Column name */ + char const **pzDataType, /* OUTPUT: Declared data type */ + char const **pzCollSeq, /* OUTPUT: Collation sequence name */ + int *pNotNull, /* OUTPUT: True if NOT NULL constraint exists */ + int *pPrimaryKey, /* OUTPUT: True if column part of PK */ + int *pAutoinc /* OUTPUT: True if column is auto-increment */ +); + +/* +** CAPI3REF: Load An Extension {H12600} +** +** This interface loads an SQLite extension library from the named file. +** +** {H12601} The sqlite3_load_extension() interface attempts to load an +** SQLite extension library contained in the file zFile. +** +** {H12602} The entry point is zProc. +** +** {H12603} zProc may be 0, in which case the name of the entry point +** defaults to "sqlite3_extension_init". +** +** {H12604} The sqlite3_load_extension() interface shall return +** [SQLITE_OK] on success and [SQLITE_ERROR] if something goes wrong. +** +** {H12605} If an error occurs and pzErrMsg is not 0, then the +** [sqlite3_load_extension()] interface shall attempt to +** fill *pzErrMsg with error message text stored in memory +** obtained from [sqlite3_malloc()]. {END} The calling function +** should free this memory by calling [sqlite3_free()]. +** +** {H12606} Extension loading must be enabled using +** [sqlite3_enable_load_extension()] prior to calling this API, +** otherwise an error will be returned. +*/ +SQLITE_API int sqlite3_load_extension( + sqlite3 *db, /* Load the extension into this database connection */ + const char *zFile, /* Name of the shared library containing extension */ + const char *zProc, /* Entry point. Derived from zFile if 0 */ + char **pzErrMsg /* Put error message here if not 0 */ +); + +/* +** CAPI3REF: Enable Or Disable Extension Loading {H12620} +** +** So as not to open security holes in older applications that are +** unprepared to deal with extension loading, and as a means of disabling +** extension loading while evaluating user-entered SQL, the following API +** is provided to turn the [sqlite3_load_extension()] mechanism on and off. +** +** Extension loading is off by default. See ticket #1863. +** +** {H12621} Call the sqlite3_enable_load_extension() routine with onoff==1 +** to turn extension loading on and call it with onoff==0 to turn +** it back off again. +** +** {H12622} Extension loading is off by default. +*/ +SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff); + +/* +** CAPI3REF: Automatically Load An Extensions {H12640} +** +** This API can be invoked at program startup in order to register +** one or more statically linked extensions that will be available +** to all new [database connections]. {END} +** +** This routine stores a pointer to the extension in an array that is +** obtained from [sqlite3_malloc()]. If you run a memory leak checker +** on your program and it reports a leak because of this array, invoke +** [sqlite3_reset_auto_extension()] prior to shutdown to free the memory. +** +** {H12641} This function registers an extension entry point that is +** automatically invoked whenever a new [database connection] +** is opened using [sqlite3_open()], [sqlite3_open16()], +** or [sqlite3_open_v2()]. +** +** {H12642} Duplicate extensions are detected so calling this routine +** multiple times with the same extension is harmless. +** +** {H12643} This routine stores a pointer to the extension in an array +** that is obtained from [sqlite3_malloc()]. +** +** {H12644} Automatic extensions apply across all threads. +*/ +SQLITE_API int sqlite3_auto_extension(void (*xEntryPoint)(void)); + +/* +** CAPI3REF: Reset Automatic Extension Loading {H12660} +** +** This function disables all previously registered automatic +** extensions. {END} It undoes the effect of all prior +** [sqlite3_auto_extension()] calls. +** +** {H12661} This function disables all previously registered +** automatic extensions. +** +** {H12662} This function disables automatic extensions in all threads. +*/ +SQLITE_API void sqlite3_reset_auto_extension(void); + +/* +****** EXPERIMENTAL - subject to change without notice ************** +** +** The interface to the virtual-table mechanism is currently considered +** to be experimental. The interface might change in incompatible ways. +** If this is a problem for you, do not use the interface at this time. +** +** When the virtual-table mechanism stabilizes, we will declare the +** interface fixed, support it indefinitely, and remove this comment. +*/ + +/* +** Structures used by the virtual table interface +*/ +typedef struct sqlite3_vtab sqlite3_vtab; +typedef struct sqlite3_index_info sqlite3_index_info; +typedef struct sqlite3_vtab_cursor sqlite3_vtab_cursor; +typedef struct sqlite3_module sqlite3_module; + +/* +** CAPI3REF: Virtual Table Object {H18000} +** KEYWORDS: sqlite3_module {virtual table module} +** EXPERIMENTAL +** +** This structure, sometimes called a a "virtual table module", +** defines the implementation of a [virtual tables]. +** This structure consists mostly of methods for the module. +** +** A virtual table module is created by filling in a persistent +** instance of this structure and passing a pointer to that instance +** to [sqlite3_create_module()] or [sqlite3_create_module_v2()]. +** The registration remains valid until it is replaced by a different +** module or until the [database connection] closes. The content +** of this structure must not change while it is registered with +** any database connection. +*/ +struct sqlite3_module { + int iVersion; + int (*xCreate)(sqlite3*, void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVTab, char**); + int (*xConnect)(sqlite3*, void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVTab, char**); + int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*); + int (*xDisconnect)(sqlite3_vtab *pVTab); + int (*xDestroy)(sqlite3_vtab *pVTab); + int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor); + int (*xClose)(sqlite3_vtab_cursor*); + int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr, + int argc, sqlite3_value **argv); + int (*xNext)(sqlite3_vtab_cursor*); + int (*xEof)(sqlite3_vtab_cursor*); + int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int); + int (*xRowid)(sqlite3_vtab_cursor*, sqlite3_int64 *pRowid); + int (*xUpdate)(sqlite3_vtab *, int, sqlite3_value **, sqlite3_int64 *); + int (*xBegin)(sqlite3_vtab *pVTab); + int (*xSync)(sqlite3_vtab *pVTab); + int (*xCommit)(sqlite3_vtab *pVTab); + int (*xRollback)(sqlite3_vtab *pVTab); + int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName, + void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), + void **ppArg); + int (*xRename)(sqlite3_vtab *pVtab, const char *zNew); +}; + +/* +** CAPI3REF: Virtual Table Indexing Information {H18100} +** KEYWORDS: sqlite3_index_info +** EXPERIMENTAL +** +** The sqlite3_index_info structure and its substructures is used to +** pass information into and receive the reply from the [xBestIndex] +** method of a [virtual table module]. The fields under **Inputs** are the +** inputs to xBestIndex and are read-only. xBestIndex inserts its +** results into the **Outputs** fields. +** +** The aConstraint[] array records WHERE clause constraints of the form: +** +**
column OP expr
+** +** where OP is =, <, <=, >, or >=. The particular operator is +** stored in aConstraint[].op. The index of the column is stored in +** aConstraint[].iColumn. aConstraint[].usable is TRUE if the +** expr on the right-hand side can be evaluated (and thus the constraint +** is usable) and false if it cannot. +** +** The optimizer automatically inverts terms of the form "expr OP column" +** and makes other simplifications to the WHERE clause in an attempt to +** get as many WHERE clause terms into the form shown above as possible. +** The aConstraint[] array only reports WHERE clause terms in the correct +** form that refer to the particular virtual table being queried. +** +** Information about the ORDER BY clause is stored in aOrderBy[]. +** Each term of aOrderBy records a column of the ORDER BY clause. +** +** The [xBestIndex] method must fill aConstraintUsage[] with information +** about what parameters to pass to xFilter. If argvIndex>0 then +** the right-hand side of the corresponding aConstraint[] is evaluated +** and becomes the argvIndex-th entry in argv. If aConstraintUsage[].omit +** is true, then the constraint is assumed to be fully handled by the +** virtual table and is not checked again by SQLite. +** +** The idxNum and idxPtr values are recorded and passed into the +** [xFilter] method. +** [sqlite3_free()] is used to free idxPtr if and only iff +** needToFreeIdxPtr is true. +** +** The orderByConsumed means that output from [xFilter]/[xNext] will occur in +** the correct order to satisfy the ORDER BY clause so that no separate +** sorting step is required. +** +** The estimatedCost value is an estimate of the cost of doing the +** particular lookup. A full scan of a table with N entries should have +** a cost of N. A binary search of a table of N entries should have a +** cost of approximately log(N). +*/ +struct sqlite3_index_info { + /* Inputs */ + int nConstraint; /* Number of entries in aConstraint */ + struct sqlite3_index_constraint { + int iColumn; /* Column on left-hand side of constraint */ + unsigned char op; /* Constraint operator */ + unsigned char usable; /* True if this constraint is usable */ + int iTermOffset; /* Used internally - xBestIndex should ignore */ + } *aConstraint; /* Table of WHERE clause constraints */ + int nOrderBy; /* Number of terms in the ORDER BY clause */ + struct sqlite3_index_orderby { + int iColumn; /* Column number */ + unsigned char desc; /* True for DESC. False for ASC. */ + } *aOrderBy; /* The ORDER BY clause */ + /* Outputs */ + struct sqlite3_index_constraint_usage { + int argvIndex; /* if >0, constraint is part of argv to xFilter */ + unsigned char omit; /* Do not code a test for this constraint */ + } *aConstraintUsage; + int idxNum; /* Number used to identify the index */ + char *idxStr; /* String, possibly obtained from sqlite3_malloc */ + int needToFreeIdxStr; /* Free idxStr using sqlite3_free() if true */ + int orderByConsumed; /* True if output is already ordered */ + double estimatedCost; /* Estimated cost of using this index */ +}; +#define SQLITE_INDEX_CONSTRAINT_EQ 2 +#define SQLITE_INDEX_CONSTRAINT_GT 4 +#define SQLITE_INDEX_CONSTRAINT_LE 8 +#define SQLITE_INDEX_CONSTRAINT_LT 16 +#define SQLITE_INDEX_CONSTRAINT_GE 32 +#define SQLITE_INDEX_CONSTRAINT_MATCH 64 + +/* +** CAPI3REF: Register A Virtual Table Implementation {H18200} +** EXPERIMENTAL +** +** This routine is used to register a new [virtual table module] name. +** Module names must be registered before +** creating a new [virtual table] using the module, or before using a +** preexisting [virtual table] for the module. +** +** The module name is registered on the [database connection] specified +** by the first parameter. The name of the module is given by the +** second parameter. The third parameter is a pointer to +** the implementation of the [virtual table module]. The fourth +** parameter is an arbitrary client data pointer that is passed through +** into the [xCreate] and [xConnect] methods of the virtual table module +** when a new virtual table is be being created or reinitialized. +** +** This interface has exactly the same effect as calling +** [sqlite3_create_module_v2()] with a NULL client data destructor. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_create_module( + sqlite3 *db, /* SQLite connection to register module with */ + const char *zName, /* Name of the module */ + const sqlite3_module *p, /* Methods for the module */ + void *pClientData /* Client data for xCreate/xConnect */ +); + +/* +** CAPI3REF: Register A Virtual Table Implementation {H18210} +** EXPERIMENTAL +** +** This routine is identical to the [sqlite3_create_module()] method, +** except that it has an extra parameter to specify +** a destructor function for the client data pointer. SQLite will +** invoke the destructor function (if it is not NULL) when SQLite +** no longer needs the pClientData pointer. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_create_module_v2( + sqlite3 *db, /* SQLite connection to register module with */ + const char *zName, /* Name of the module */ + const sqlite3_module *p, /* Methods for the module */ + void *pClientData, /* Client data for xCreate/xConnect */ + void(*xDestroy)(void*) /* Module destructor function */ +); + +/* +** CAPI3REF: Virtual Table Instance Object {H18010} +** KEYWORDS: sqlite3_vtab +** EXPERIMENTAL +** +** Every [virtual table module] implementation uses a subclass +** of the following structure to describe a particular instance +** of the [virtual table]. Each subclass will +** be tailored to the specific needs of the module implementation. +** The purpose of this superclass is to define certain fields that are +** common to all module implementations. +** +** Virtual tables methods can set an error message by assigning a +** string obtained from [sqlite3_mprintf()] to zErrMsg. The method should +** take care that any prior string is freed by a call to [sqlite3_free()] +** prior to assigning a new string to zErrMsg. After the error message +** is delivered up to the client application, the string will be automatically +** freed by sqlite3_free() and the zErrMsg field will be zeroed. +*/ +struct sqlite3_vtab { + const sqlite3_module *pModule; /* The module for this virtual table */ + int nRef; /* NO LONGER USED */ + char *zErrMsg; /* Error message from sqlite3_mprintf() */ + /* Virtual table implementations will typically add additional fields */ +}; + +/* +** CAPI3REF: Virtual Table Cursor Object {H18020} +** KEYWORDS: sqlite3_vtab_cursor {virtual table cursor} +** EXPERIMENTAL +** +** Every [virtual table module] implementation uses a subclass of the +** following structure to describe cursors that point into the +** [virtual table] and are used +** to loop through the virtual table. Cursors are created using the +** [sqlite3_module.xOpen | xOpen] method of the module and are destroyed +** by the [sqlite3_module.xClose | xClose] method. Cussors are used +** by the [xFilter], [xNext], [xEof], [xColumn], and [xRowid] methods +** of the module. Each module implementation will define +** the content of a cursor structure to suit its own needs. +** +** This superclass exists in order to define fields of the cursor that +** are common to all implementations. +*/ +struct sqlite3_vtab_cursor { + sqlite3_vtab *pVtab; /* Virtual table of this cursor */ + /* Virtual table implementations will typically add additional fields */ +}; + +/* +** CAPI3REF: Declare The Schema Of A Virtual Table {H18280} +** EXPERIMENTAL +** +** The [xCreate] and [xConnect] methods of a +** [virtual table module] call this interface +** to declare the format (the names and datatypes of the columns) of +** the virtual tables they implement. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_declare_vtab(sqlite3*, const char *zSQL); + +/* +** CAPI3REF: Overload A Function For A Virtual Table {H18300} +** EXPERIMENTAL +** +** Virtual tables can provide alternative implementations of functions +** using the [xFindFunction] method of the [virtual table module]. +** But global versions of those functions +** must exist in order to be overloaded. +** +** This API makes sure a global version of a function with a particular +** name and number of parameters exists. If no such function exists +** before this API is called, a new function is created. The implementation +** of the new function always causes an exception to be thrown. So +** the new function is not good for anything by itself. Its only +** purpose is to be a placeholder function that can be overloaded +** by a [virtual table]. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg); + +/* +** The interface to the virtual-table mechanism defined above (back up +** to a comment remarkably similar to this one) is currently considered +** to be experimental. The interface might change in incompatible ways. +** If this is a problem for you, do not use the interface at this time. +** +** When the virtual-table mechanism stabilizes, we will declare the +** interface fixed, support it indefinitely, and remove this comment. +** +****** EXPERIMENTAL - subject to change without notice ************** +*/ + +/* +** CAPI3REF: A Handle To An Open BLOB {H17800} +** KEYWORDS: {BLOB handle} {BLOB handles} +** +** An instance of this object represents an open BLOB on which +** [sqlite3_blob_open | incremental BLOB I/O] can be performed. +** Objects of this type are created by [sqlite3_blob_open()] +** and destroyed by [sqlite3_blob_close()]. +** The [sqlite3_blob_read()] and [sqlite3_blob_write()] interfaces +** can be used to read or write small subsections of the BLOB. +** The [sqlite3_blob_bytes()] interface returns the size of the BLOB in bytes. +*/ +typedef struct sqlite3_blob sqlite3_blob; + +/* +** CAPI3REF: Open A BLOB For Incremental I/O {H17810} +** +** This interfaces opens a [BLOB handle | handle] to the BLOB located +** in row iRow, column zColumn, table zTable in database zDb; +** in other words, the same BLOB that would be selected by: +** +**
+**     SELECT zColumn FROM zDb.zTable WHERE [rowid] = iRow;
+** 
{END} +** +** If the flags parameter is non-zero, then the BLOB is opened for read +** and write access. If it is zero, the BLOB is opened for read access. +** +** Note that the database name is not the filename that contains +** the database but rather the symbolic name of the database that +** is assigned when the database is connected using [ATTACH]. +** For the main database file, the database name is "main". +** For TEMP tables, the database name is "temp". +** +** On success, [SQLITE_OK] is returned and the new [BLOB handle] is written +** to *ppBlob. Otherwise an [error code] is returned and *ppBlob is set +** to be a null pointer. +** This function sets the [database connection] error code and message +** accessible via [sqlite3_errcode()] and [sqlite3_errmsg()] and related +** functions. Note that the *ppBlob variable is always initialized in a +** way that makes it safe to invoke [sqlite3_blob_close()] on *ppBlob +** regardless of the success or failure of this routine. +** +** If the row that a BLOB handle points to is modified by an +** [UPDATE], [DELETE], or by [ON CONFLICT] side-effects +** then the BLOB handle is marked as "expired". +** This is true if any column of the row is changed, even a column +** other than the one the BLOB handle is open on. +** Calls to [sqlite3_blob_read()] and [sqlite3_blob_write()] for +** a expired BLOB handle fail with an return code of [SQLITE_ABORT]. +** Changes written into a BLOB prior to the BLOB expiring are not +** rollback by the expiration of the BLOB. Such changes will eventually +** commit if the transaction continues to completion. +** +** Use the [sqlite3_blob_bytes()] interface to determine the size of +** the opened blob. The size of a blob may not be changed by this +** interface. Use the [UPDATE] SQL command to change the size of a +** blob. +** +** The [sqlite3_bind_zeroblob()] and [sqlite3_result_zeroblob()] interfaces +** and the built-in [zeroblob] SQL function can be used, if desired, +** to create an empty, zero-filled blob in which to read or write using +** this interface. +** +** To avoid a resource leak, every open [BLOB handle] should eventually +** be released by a call to [sqlite3_blob_close()]. +** +** Requirements: +** [H17813] [H17814] [H17816] [H17819] [H17821] [H17824] +*/ +SQLITE_API int sqlite3_blob_open( + sqlite3*, + const char *zDb, + const char *zTable, + const char *zColumn, + sqlite3_int64 iRow, + int flags, + sqlite3_blob **ppBlob +); + +/* +** CAPI3REF: Close A BLOB Handle {H17830} +** +** Closes an open [BLOB handle]. +** +** Closing a BLOB shall cause the current transaction to commit +** if there are no other BLOBs, no pending prepared statements, and the +** database connection is in [autocommit mode]. +** If any writes were made to the BLOB, they might be held in cache +** until the close operation if they will fit. +** +** Closing the BLOB often forces the changes +** out to disk and so if any I/O errors occur, they will likely occur +** at the time when the BLOB is closed. Any errors that occur during +** closing are reported as a non-zero return value. +** +** The BLOB is closed unconditionally. Even if this routine returns +** an error code, the BLOB is still closed. +** +** Calling this routine with a null pointer (which as would be returned +** by failed call to [sqlite3_blob_open()]) is a harmless no-op. +** +** Requirements: +** [H17833] [H17836] [H17839] +*/ +SQLITE_API int sqlite3_blob_close(sqlite3_blob *); + +/* +** CAPI3REF: Return The Size Of An Open BLOB {H17840} +** +** Returns the size in bytes of the BLOB accessible via the +** successfully opened [BLOB handle] in its only argument. The +** incremental blob I/O routines can only read or overwriting existing +** blob content; they cannot change the size of a blob. +** +** This routine only works on a [BLOB handle] which has been created +** by a prior successful call to [sqlite3_blob_open()] and which has not +** been closed by [sqlite3_blob_close()]. Passing any other pointer in +** to this routine results in undefined and probably undesirable behavior. +** +** Requirements: +** [H17843] +*/ +SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *); + +/* +** CAPI3REF: Read Data From A BLOB Incrementally {H17850} +** +** This function is used to read data from an open [BLOB handle] into a +** caller-supplied buffer. N bytes of data are copied into buffer Z +** from the open BLOB, starting at offset iOffset. +** +** If offset iOffset is less than N bytes from the end of the BLOB, +** [SQLITE_ERROR] is returned and no data is read. If N or iOffset is +** less than zero, [SQLITE_ERROR] is returned and no data is read. +** The size of the blob (and hence the maximum value of N+iOffset) +** can be determined using the [sqlite3_blob_bytes()] interface. +** +** An attempt to read from an expired [BLOB handle] fails with an +** error code of [SQLITE_ABORT]. +** +** On success, SQLITE_OK is returned. +** Otherwise, an [error code] or an [extended error code] is returned. +** +** This routine only works on a [BLOB handle] which has been created +** by a prior successful call to [sqlite3_blob_open()] and which has not +** been closed by [sqlite3_blob_close()]. Passing any other pointer in +** to this routine results in undefined and probably undesirable behavior. +** +** See also: [sqlite3_blob_write()]. +** +** Requirements: +** [H17853] [H17856] [H17859] [H17862] [H17863] [H17865] [H17868] +*/ +SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); + +/* +** CAPI3REF: Write Data Into A BLOB Incrementally {H17870} +** +** This function is used to write data into an open [BLOB handle] from a +** caller-supplied buffer. N bytes of data are copied from the buffer Z +** into the open BLOB, starting at offset iOffset. +** +** If the [BLOB handle] passed as the first argument was not opened for +** writing (the flags parameter to [sqlite3_blob_open()] was zero), +** this function returns [SQLITE_READONLY]. +** +** This function may only modify the contents of the BLOB; it is +** not possible to increase the size of a BLOB using this API. +** If offset iOffset is less than N bytes from the end of the BLOB, +** [SQLITE_ERROR] is returned and no data is written. If N is +** less than zero [SQLITE_ERROR] is returned and no data is written. +** The size of the BLOB (and hence the maximum value of N+iOffset) +** can be determined using the [sqlite3_blob_bytes()] interface. +** +** An attempt to write to an expired [BLOB handle] fails with an +** error code of [SQLITE_ABORT]. Writes to the BLOB that occurred +** before the [BLOB handle] expired are not rolled back by the +** expiration of the handle, though of course those changes might +** have been overwritten by the statement that expired the BLOB handle +** or by other independent statements. +** +** On success, SQLITE_OK is returned. +** Otherwise, an [error code] or an [extended error code] is returned. +** +** This routine only works on a [BLOB handle] which has been created +** by a prior successful call to [sqlite3_blob_open()] and which has not +** been closed by [sqlite3_blob_close()]. Passing any other pointer in +** to this routine results in undefined and probably undesirable behavior. +** +** See also: [sqlite3_blob_read()]. +** +** Requirements: +** [H17873] [H17874] [H17875] [H17876] [H17877] [H17879] [H17882] [H17885] +** [H17888] +*/ +SQLITE_API int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset); + +/* +** CAPI3REF: Virtual File System Objects {H11200} +** +** A virtual filesystem (VFS) is an [sqlite3_vfs] object +** that SQLite uses to interact +** with the underlying operating system. Most SQLite builds come with a +** single default VFS that is appropriate for the host computer. +** New VFSes can be registered and existing VFSes can be unregistered. +** The following interfaces are provided. +** +** The sqlite3_vfs_find() interface returns a pointer to a VFS given its name. +** Names are case sensitive. +** Names are zero-terminated UTF-8 strings. +** If there is no match, a NULL pointer is returned. +** If zVfsName is NULL then the default VFS is returned. +** +** New VFSes are registered with sqlite3_vfs_register(). +** Each new VFS becomes the default VFS if the makeDflt flag is set. +** The same VFS can be registered multiple times without injury. +** To make an existing VFS into the default VFS, register it again +** with the makeDflt flag set. If two different VFSes with the +** same name are registered, the behavior is undefined. If a +** VFS is registered with a name that is NULL or an empty string, +** then the behavior is undefined. +** +** Unregister a VFS with the sqlite3_vfs_unregister() interface. +** If the default VFS is unregistered, another VFS is chosen as +** the default. The choice for the new VFS is arbitrary. +** +** Requirements: +** [H11203] [H11206] [H11209] [H11212] [H11215] [H11218] +*/ +SQLITE_API sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName); +SQLITE_API int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt); +SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); + +/* +** CAPI3REF: Mutexes {H17000} +** +** The SQLite core uses these routines for thread +** synchronization. Though they are intended for internal +** use by SQLite, code that links against SQLite is +** permitted to use any of these routines. +** +** The SQLite source code contains multiple implementations +** of these mutex routines. An appropriate implementation +** is selected automatically at compile-time. The following +** implementations are available in the SQLite core: +** +**
    +**
  • SQLITE_MUTEX_OS2 +**
  • SQLITE_MUTEX_PTHREAD +**
  • SQLITE_MUTEX_W32 +**
  • SQLITE_MUTEX_NOOP +**
+** +** The SQLITE_MUTEX_NOOP implementation is a set of routines +** that does no real locking and is appropriate for use in +** a single-threaded application. The SQLITE_MUTEX_OS2, +** SQLITE_MUTEX_PTHREAD, and SQLITE_MUTEX_W32 implementations +** are appropriate for use on OS/2, Unix, and Windows. +** +** If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor +** macro defined (with "-DSQLITE_MUTEX_APPDEF=1"), then no mutex +** implementation is included with the library. In this case the +** application must supply a custom mutex implementation using the +** [SQLITE_CONFIG_MUTEX] option of the sqlite3_config() function +** before calling sqlite3_initialize() or any other public sqlite3_ +** function that calls sqlite3_initialize(). +** +** {H17011} The sqlite3_mutex_alloc() routine allocates a new +** mutex and returns a pointer to it. {H17012} If it returns NULL +** that means that a mutex could not be allocated. {H17013} SQLite +** will unwind its stack and return an error. {H17014} The argument +** to sqlite3_mutex_alloc() is one of these integer constants: +** +**
    +**
  • SQLITE_MUTEX_FAST +**
  • SQLITE_MUTEX_RECURSIVE +**
  • SQLITE_MUTEX_STATIC_MASTER +**
  • SQLITE_MUTEX_STATIC_MEM +**
  • SQLITE_MUTEX_STATIC_MEM2 +**
  • SQLITE_MUTEX_STATIC_PRNG +**
  • SQLITE_MUTEX_STATIC_LRU +**
  • SQLITE_MUTEX_STATIC_LRU2 +**
+** +** {H17015} The first two constants cause sqlite3_mutex_alloc() to create +** a new mutex. The new mutex is recursive when SQLITE_MUTEX_RECURSIVE +** is used but not necessarily so when SQLITE_MUTEX_FAST is used. {END} +** The mutex implementation does not need to make a distinction +** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does +** not want to. {H17016} But SQLite will only request a recursive mutex in +** cases where it really needs one. {END} If a faster non-recursive mutex +** implementation is available on the host platform, the mutex subsystem +** might return such a mutex in response to SQLITE_MUTEX_FAST. +** +** {H17017} The other allowed parameters to sqlite3_mutex_alloc() each return +** a pointer to a static preexisting mutex. {END} Six static mutexes are +** used by the current version of SQLite. Future versions of SQLite +** may add additional static mutexes. Static mutexes are for internal +** use by SQLite only. Applications that use SQLite mutexes should +** use only the dynamic mutexes returned by SQLITE_MUTEX_FAST or +** SQLITE_MUTEX_RECURSIVE. +** +** {H17018} Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST +** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc() +** returns a different mutex on every call. {H17034} But for the static +** mutex types, the same mutex is returned on every call that has +** the same type number. +** +** {H17019} The sqlite3_mutex_free() routine deallocates a previously +** allocated dynamic mutex. {H17020} SQLite is careful to deallocate every +** dynamic mutex that it allocates. {A17021} The dynamic mutexes must not be in +** use when they are deallocated. {A17022} Attempting to deallocate a static +** mutex results in undefined behavior. {H17023} SQLite never deallocates +** a static mutex. {END} +** +** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt +** to enter a mutex. {H17024} If another thread is already within the mutex, +** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return +** SQLITE_BUSY. {H17025} The sqlite3_mutex_try() interface returns [SQLITE_OK] +** upon successful entry. {H17026} Mutexes created using +** SQLITE_MUTEX_RECURSIVE can be entered multiple times by the same thread. +** {H17027} In such cases the, +** mutex must be exited an equal number of times before another thread +** can enter. {A17028} If the same thread tries to enter any other +** kind of mutex more than once, the behavior is undefined. +** {H17029} SQLite will never exhibit +** such behavior in its own use of mutexes. +** +** Some systems (for example, Windows 95) do not support the operation +** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try() +** will always return SQLITE_BUSY. {H17030} The SQLite core only ever uses +** sqlite3_mutex_try() as an optimization so this is acceptable behavior. +** +** {H17031} The sqlite3_mutex_leave() routine exits a mutex that was +** previously entered by the same thread. {A17032} The behavior +** is undefined if the mutex is not currently entered by the +** calling thread or is not currently allocated. {H17033} SQLite will +** never do either. {END} +** +** If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), or +** sqlite3_mutex_leave() is a NULL pointer, then all three routines +** behave as no-ops. +** +** See also: [sqlite3_mutex_held()] and [sqlite3_mutex_notheld()]. +*/ +SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int); +SQLITE_API void sqlite3_mutex_free(sqlite3_mutex*); +SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex*); +SQLITE_API int sqlite3_mutex_try(sqlite3_mutex*); +SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*); + +/* +** CAPI3REF: Mutex Methods Object {H17120} +** EXPERIMENTAL +** +** An instance of this structure defines the low-level routines +** used to allocate and use mutexes. +** +** Usually, the default mutex implementations provided by SQLite are +** sufficient, however the user has the option of substituting a custom +** implementation for specialized deployments or systems for which SQLite +** does not provide a suitable implementation. In this case, the user +** creates and populates an instance of this structure to pass +** to sqlite3_config() along with the [SQLITE_CONFIG_MUTEX] option. +** Additionally, an instance of this structure can be used as an +** output variable when querying the system for the current mutex +** implementation, using the [SQLITE_CONFIG_GETMUTEX] option. +** +** The xMutexInit method defined by this structure is invoked as +** part of system initialization by the sqlite3_initialize() function. +** {H17001} The xMutexInit routine shall be called by SQLite once for each +** effective call to [sqlite3_initialize()]. +** +** The xMutexEnd method defined by this structure is invoked as +** part of system shutdown by the sqlite3_shutdown() function. The +** implementation of this method is expected to release all outstanding +** resources obtained by the mutex methods implementation, especially +** those obtained by the xMutexInit method. {H17003} The xMutexEnd() +** interface shall be invoked once for each call to [sqlite3_shutdown()]. +** +** The remaining seven methods defined by this structure (xMutexAlloc, +** xMutexFree, xMutexEnter, xMutexTry, xMutexLeave, xMutexHeld and +** xMutexNotheld) implement the following interfaces (respectively): +** +**
    +**
  • [sqlite3_mutex_alloc()]
  • +**
  • [sqlite3_mutex_free()]
  • +**
  • [sqlite3_mutex_enter()]
  • +**
  • [sqlite3_mutex_try()]
  • +**
  • [sqlite3_mutex_leave()]
  • +**
  • [sqlite3_mutex_held()]
  • +**
  • [sqlite3_mutex_notheld()]
  • +**
+** +** The only difference is that the public sqlite3_XXX functions enumerated +** above silently ignore any invocations that pass a NULL pointer instead +** of a valid mutex handle. The implementations of the methods defined +** by this structure are not required to handle this case, the results +** of passing a NULL pointer instead of a valid mutex handle are undefined +** (i.e. it is acceptable to provide an implementation that segfaults if +** it is passed a NULL pointer). +** +** The xMutexInit() method must be threadsafe. It must be harmless to +** invoke xMutexInit() mutiple times within the same process and without +** intervening calls to xMutexEnd(). Second and subsequent calls to +** xMutexInit() must be no-ops. +** +** xMutexInit() must not use SQLite memory allocation ([sqlite3_malloc()] +** and its associates). Similarly, xMutexAlloc() must not use SQLite memory +** allocation for a static mutex. However xMutexAlloc() may use SQLite +** memory allocation for a fast or recursive mutex. +** +** SQLite will invoke the xMutexEnd() method when [sqlite3_shutdown()] is +** called, but only if the prior call to xMutexInit returned SQLITE_OK. +** If xMutexInit fails in any way, it is expected to clean up after itself +** prior to returning. +*/ +typedef struct sqlite3_mutex_methods sqlite3_mutex_methods; +struct sqlite3_mutex_methods { + int (*xMutexInit)(void); + int (*xMutexEnd)(void); + sqlite3_mutex *(*xMutexAlloc)(int); + void (*xMutexFree)(sqlite3_mutex *); + void (*xMutexEnter)(sqlite3_mutex *); + int (*xMutexTry)(sqlite3_mutex *); + void (*xMutexLeave)(sqlite3_mutex *); + int (*xMutexHeld)(sqlite3_mutex *); + int (*xMutexNotheld)(sqlite3_mutex *); +}; + +/* +** CAPI3REF: Mutex Verification Routines {H17080} +** +** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routines +** are intended for use inside assert() statements. {H17081} The SQLite core +** never uses these routines except inside an assert() and applications +** are advised to follow the lead of the core. {H17082} The core only +** provides implementations for these routines when it is compiled +** with the SQLITE_DEBUG flag. {A17087} External mutex implementations +** are only required to provide these routines if SQLITE_DEBUG is +** defined and if NDEBUG is not defined. +** +** {H17083} These routines should return true if the mutex in their argument +** is held or not held, respectively, by the calling thread. +** +** {X17084} The implementation is not required to provided versions of these +** routines that actually work. If the implementation does not provide working +** versions of these routines, it should at least provide stubs that always +** return true so that one does not get spurious assertion failures. +** +** {H17085} If the argument to sqlite3_mutex_held() is a NULL pointer then +** the routine should return 1. {END} This seems counter-intuitive since +** clearly the mutex cannot be held if it does not exist. But the +** the reason the mutex does not exist is because the build is not +** using mutexes. And we do not want the assert() containing the +** call to sqlite3_mutex_held() to fail, so a non-zero return is +** the appropriate thing to do. {H17086} The sqlite3_mutex_notheld() +** interface should also return 1 when given a NULL pointer. +*/ +SQLITE_API int sqlite3_mutex_held(sqlite3_mutex*); +SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*); + +/* +** CAPI3REF: Mutex Types {H17001} +** +** The [sqlite3_mutex_alloc()] interface takes a single argument +** which is one of these integer constants. +** +** The set of static mutexes may change from one SQLite release to the +** next. Applications that override the built-in mutex logic must be +** prepared to accommodate additional static mutexes. +*/ +#define SQLITE_MUTEX_FAST 0 +#define SQLITE_MUTEX_RECURSIVE 1 +#define SQLITE_MUTEX_STATIC_MASTER 2 +#define SQLITE_MUTEX_STATIC_MEM 3 /* sqlite3_malloc() */ +#define SQLITE_MUTEX_STATIC_MEM2 4 /* NOT USED */ +#define SQLITE_MUTEX_STATIC_OPEN 4 /* sqlite3BtreeOpen() */ +#define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_random() */ +#define SQLITE_MUTEX_STATIC_LRU 6 /* lru page list */ +#define SQLITE_MUTEX_STATIC_LRU2 7 /* lru page list */ + +/* +** CAPI3REF: Retrieve the mutex for a database connection {H17002} +** +** This interface returns a pointer the [sqlite3_mutex] object that +** serializes access to the [database connection] given in the argument +** when the [threading mode] is Serialized. +** If the [threading mode] is Single-thread or Multi-thread then this +** routine returns a NULL pointer. +*/ +SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3*); + +/* +** CAPI3REF: Low-Level Control Of Database Files {H11300} +** +** {H11301} The [sqlite3_file_control()] interface makes a direct call to the +** xFileControl method for the [sqlite3_io_methods] object associated +** with a particular database identified by the second argument. {H11302} The +** name of the database is the name assigned to the database by the +** ATTACH SQL command that opened the +** database. {H11303} To control the main database file, use the name "main" +** or a NULL pointer. {H11304} The third and fourth parameters to this routine +** are passed directly through to the second and third parameters of +** the xFileControl method. {H11305} The return value of the xFileControl +** method becomes the return value of this routine. +** +** {H11306} If the second parameter (zDbName) does not match the name of any +** open database file, then SQLITE_ERROR is returned. {H11307} This error +** code is not remembered and will not be recalled by [sqlite3_errcode()] +** or [sqlite3_errmsg()]. {A11308} The underlying xFileControl method might +** also return SQLITE_ERROR. {A11309} There is no way to distinguish between +** an incorrect zDbName and an SQLITE_ERROR return from the underlying +** xFileControl method. {END} +** +** See also: [SQLITE_FCNTL_LOCKSTATE] +*/ +SQLITE_API int sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*); + +/* +** CAPI3REF: Testing Interface {H11400} +** +** The sqlite3_test_control() interface is used to read out internal +** state of SQLite and to inject faults into SQLite for testing +** purposes. The first parameter is an operation code that determines +** the number, meaning, and operation of all subsequent parameters. +** +** This interface is not for use by applications. It exists solely +** for verifying the correct operation of the SQLite library. Depending +** on how the SQLite library is compiled, this interface might not exist. +** +** The details of the operation codes, their meanings, the parameters +** they take, and what they do are all subject to change without notice. +** Unlike most of the SQLite API, this function is not guaranteed to +** operate consistently from one release to the next. +*/ +SQLITE_API int sqlite3_test_control(int op, ...); + +/* +** CAPI3REF: Testing Interface Operation Codes {H11410} +** +** These constants are the valid operation code parameters used +** as the first argument to [sqlite3_test_control()]. +** +** These parameters and their meanings are subject to change +** without notice. These values are for testing purposes only. +** Applications should not use any of these parameters or the +** [sqlite3_test_control()] interface. +*/ +#define SQLITE_TESTCTRL_PRNG_SAVE 5 +#define SQLITE_TESTCTRL_PRNG_RESTORE 6 +#define SQLITE_TESTCTRL_PRNG_RESET 7 +#define SQLITE_TESTCTRL_BITVEC_TEST 8 +#define SQLITE_TESTCTRL_FAULT_INSTALL 9 +#define SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS 10 +#define SQLITE_TESTCTRL_PENDING_BYTE 11 +#define SQLITE_TESTCTRL_ASSERT 12 +#define SQLITE_TESTCTRL_ALWAYS 13 +#define SQLITE_TESTCTRL_RESERVE 14 + +/* +** CAPI3REF: SQLite Runtime Status {H17200} +** EXPERIMENTAL +** +** This interface is used to retrieve runtime status information +** about the preformance of SQLite, and optionally to reset various +** highwater marks. The first argument is an integer code for +** the specific parameter to measure. Recognized integer codes +** are of the form [SQLITE_STATUS_MEMORY_USED | SQLITE_STATUS_...]. +** The current value of the parameter is returned into *pCurrent. +** The highest recorded value is returned in *pHighwater. If the +** resetFlag is true, then the highest record value is reset after +** *pHighwater is written. Some parameters do not record the highest +** value. For those parameters +** nothing is written into *pHighwater and the resetFlag is ignored. +** Other parameters record only the highwater mark and not the current +** value. For these latter parameters nothing is written into *pCurrent. +** +** This routine returns SQLITE_OK on success and a non-zero +** [error code] on failure. +** +** This routine is threadsafe but is not atomic. This routine can be +** called while other threads are running the same or different SQLite +** interfaces. However the values returned in *pCurrent and +** *pHighwater reflect the status of SQLite at different points in time +** and it is possible that another thread might change the parameter +** in between the times when *pCurrent and *pHighwater are written. +** +** See also: [sqlite3_db_status()] +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag); + + +/* +** CAPI3REF: Status Parameters {H17250} +** EXPERIMENTAL +** +** These integer constants designate various run-time status parameters +** that can be returned by [sqlite3_status()]. +** +**
+**
SQLITE_STATUS_MEMORY_USED
+**
This parameter is the current amount of memory checked out +** using [sqlite3_malloc()], either directly or indirectly. The +** figure includes calls made to [sqlite3_malloc()] by the application +** and internal memory usage by the SQLite library. Scratch memory +** controlled by [SQLITE_CONFIG_SCRATCH] and auxiliary page-cache +** memory controlled by [SQLITE_CONFIG_PAGECACHE] is not included in +** this parameter. The amount returned is the sum of the allocation +** sizes as reported by the xSize method in [sqlite3_mem_methods].
+** +**
SQLITE_STATUS_MALLOC_SIZE
+**
This parameter records the largest memory allocation request +** handed to [sqlite3_malloc()] or [sqlite3_realloc()] (or their +** internal equivalents). Only the value returned in the +** *pHighwater parameter to [sqlite3_status()] is of interest. +** The value written into the *pCurrent parameter is undefined.
+** +**
SQLITE_STATUS_PAGECACHE_USED
+**
This parameter returns the number of pages used out of the +** [pagecache memory allocator] that was configured using +** [SQLITE_CONFIG_PAGECACHE]. The +** value returned is in pages, not in bytes.
+** +**
SQLITE_STATUS_PAGECACHE_OVERFLOW
+**
This parameter returns the number of bytes of page cache +** allocation which could not be statisfied by the [SQLITE_CONFIG_PAGECACHE] +** buffer and where forced to overflow to [sqlite3_malloc()]. The +** returned value includes allocations that overflowed because they +** where too large (they were larger than the "sz" parameter to +** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because +** no space was left in the page cache.
+** +**
SQLITE_STATUS_PAGECACHE_SIZE
+**
This parameter records the largest memory allocation request +** handed to [pagecache memory allocator]. Only the value returned in the +** *pHighwater parameter to [sqlite3_status()] is of interest. +** The value written into the *pCurrent parameter is undefined.
+** +**
SQLITE_STATUS_SCRATCH_USED
+**
This parameter returns the number of allocations used out of the +** [scratch memory allocator] configured using +** [SQLITE_CONFIG_SCRATCH]. The value returned is in allocations, not +** in bytes. Since a single thread may only have one scratch allocation +** outstanding at time, this parameter also reports the number of threads +** using scratch memory at the same time.
+** +**
SQLITE_STATUS_SCRATCH_OVERFLOW
+**
This parameter returns the number of bytes of scratch memory +** allocation which could not be statisfied by the [SQLITE_CONFIG_SCRATCH] +** buffer and where forced to overflow to [sqlite3_malloc()]. The values +** returned include overflows because the requested allocation was too +** larger (that is, because the requested allocation was larger than the +** "sz" parameter to [SQLITE_CONFIG_SCRATCH]) and because no scratch buffer +** slots were available. +**
+** +**
SQLITE_STATUS_SCRATCH_SIZE
+**
This parameter records the largest memory allocation request +** handed to [scratch memory allocator]. Only the value returned in the +** *pHighwater parameter to [sqlite3_status()] is of interest. +** The value written into the *pCurrent parameter is undefined.
+** +**
SQLITE_STATUS_PARSER_STACK
+**
This parameter records the deepest parser stack. It is only +** meaningful if SQLite is compiled with [YYTRACKMAXSTACKDEPTH].
+**
+** +** New status parameters may be added from time to time. +*/ +#define SQLITE_STATUS_MEMORY_USED 0 +#define SQLITE_STATUS_PAGECACHE_USED 1 +#define SQLITE_STATUS_PAGECACHE_OVERFLOW 2 +#define SQLITE_STATUS_SCRATCH_USED 3 +#define SQLITE_STATUS_SCRATCH_OVERFLOW 4 +#define SQLITE_STATUS_MALLOC_SIZE 5 +#define SQLITE_STATUS_PARSER_STACK 6 +#define SQLITE_STATUS_PAGECACHE_SIZE 7 +#define SQLITE_STATUS_SCRATCH_SIZE 8 + +/* +** CAPI3REF: Database Connection Status {H17500} +** EXPERIMENTAL +** +** This interface is used to retrieve runtime status information +** about a single [database connection]. The first argument is the +** database connection object to be interrogated. The second argument +** is the parameter to interrogate. Currently, the only allowed value +** for the second parameter is [SQLITE_DBSTATUS_LOOKASIDE_USED]. +** Additional options will likely appear in future releases of SQLite. +** +** The current value of the requested parameter is written into *pCur +** and the highest instantaneous value is written into *pHiwtr. If +** the resetFlg is true, then the highest instantaneous value is +** reset back down to the current value. +** +** See also: [sqlite3_status()] and [sqlite3_stmt_status()]. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); + +/* +** CAPI3REF: Status Parameters for database connections {H17520} +** EXPERIMENTAL +** +** These constants are the available integer "verbs" that can be passed as +** the second argument to the [sqlite3_db_status()] interface. +** +** New verbs may be added in future releases of SQLite. Existing verbs +** might be discontinued. Applications should check the return code from +** [sqlite3_db_status()] to make sure that the call worked. +** The [sqlite3_db_status()] interface will return a non-zero error code +** if a discontinued or unsupported verb is invoked. +** +**
+**
SQLITE_DBSTATUS_LOOKASIDE_USED
+**
This parameter returns the number of lookaside memory slots currently +** checked out.
+**
+*/ +#define SQLITE_DBSTATUS_LOOKASIDE_USED 0 + + +/* +** CAPI3REF: Prepared Statement Status {H17550} +** EXPERIMENTAL +** +** Each prepared statement maintains various +** [SQLITE_STMTSTATUS_SORT | counters] that measure the number +** of times it has performed specific operations. These counters can +** be used to monitor the performance characteristics of the prepared +** statements. For example, if the number of table steps greatly exceeds +** the number of table searches or result rows, that would tend to indicate +** that the prepared statement is using a full table scan rather than +** an index. +** +** This interface is used to retrieve and reset counter values from +** a [prepared statement]. The first argument is the prepared statement +** object to be interrogated. The second argument +** is an integer code for a specific [SQLITE_STMTSTATUS_SORT | counter] +** to be interrogated. +** The current value of the requested counter is returned. +** If the resetFlg is true, then the counter is reset to zero after this +** interface call returns. +** +** See also: [sqlite3_status()] and [sqlite3_db_status()]. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); + +/* +** CAPI3REF: Status Parameters for prepared statements {H17570} +** EXPERIMENTAL +** +** These preprocessor macros define integer codes that name counter +** values associated with the [sqlite3_stmt_status()] interface. +** The meanings of the various counters are as follows: +** +**
+**
SQLITE_STMTSTATUS_FULLSCAN_STEP
+**
This is the number of times that SQLite has stepped forward in +** a table as part of a full table scan. Large numbers for this counter +** may indicate opportunities for performance improvement through +** careful use of indices.
+** +**
SQLITE_STMTSTATUS_SORT
+**
This is the number of sort operations that have occurred. +** A non-zero value in this counter may indicate an opportunity to +** improvement performance through careful use of indices.
+** +**
+*/ +#define SQLITE_STMTSTATUS_FULLSCAN_STEP 1 +#define SQLITE_STMTSTATUS_SORT 2 + +/* +** CAPI3REF: Custom Page Cache Object +** EXPERIMENTAL +** +** The sqlite3_pcache type is opaque. It is implemented by +** the pluggable module. The SQLite core has no knowledge of +** its size or internal structure and never deals with the +** sqlite3_pcache object except by holding and passing pointers +** to the object. +** +** See [sqlite3_pcache_methods] for additional information. +*/ +typedef struct sqlite3_pcache sqlite3_pcache; + +/* +** CAPI3REF: Application Defined Page Cache. +** KEYWORDS: {page cache} +** EXPERIMENTAL +** +** The [sqlite3_config]([SQLITE_CONFIG_PCACHE], ...) interface can +** register an alternative page cache implementation by passing in an +** instance of the sqlite3_pcache_methods structure. The majority of the +** heap memory used by SQLite is used by the page cache to cache data read +** from, or ready to be written to, the database file. By implementing a +** custom page cache using this API, an application can control more +** precisely the amount of memory consumed by SQLite, the way in which +** that memory is allocated and released, and the policies used to +** determine exactly which parts of a database file are cached and for +** how long. +** +** The contents of the sqlite3_pcache_methods structure are copied to an +** internal buffer by SQLite within the call to [sqlite3_config]. Hence +** the application may discard the parameter after the call to +** [sqlite3_config()] returns. +** +** The xInit() method is called once for each call to [sqlite3_initialize()] +** (usually only once during the lifetime of the process). It is passed +** a copy of the sqlite3_pcache_methods.pArg value. It can be used to set +** up global structures and mutexes required by the custom page cache +** implementation. +** +** The xShutdown() method is called from within [sqlite3_shutdown()], +** if the application invokes this API. It can be used to clean up +** any outstanding resources before process shutdown, if required. +** +** SQLite holds a [SQLITE_MUTEX_RECURSIVE] mutex when it invokes +** the xInit method, so the xInit method need not be threadsafe. The +** xShutdown method is only called from [sqlite3_shutdown()] so it does +** not need to be threadsafe either. All other methods must be threadsafe +** in multithreaded applications. +** +** SQLite will never invoke xInit() more than once without an intervening +** call to xShutdown(). +** +** The xCreate() method is used to construct a new cache instance. SQLite +** will typically create one cache instance for each open database file, +** though this is not guaranteed. The +** first parameter, szPage, is the size in bytes of the pages that must +** be allocated by the cache. szPage will not be a power of two. szPage +** will the page size of the database file that is to be cached plus an +** increment (here called "R") of about 100 or 200. SQLite will use the +** extra R bytes on each page to store metadata about the underlying +** database page on disk. The value of R depends +** on the SQLite version, the target platform, and how SQLite was compiled. +** R is constant for a particular build of SQLite. The second argument to +** xCreate(), bPurgeable, is true if the cache being created will +** be used to cache database pages of a file stored on disk, or +** false if it is used for an in-memory database. The cache implementation +** does not have to do anything special based with the value of bPurgeable; +** it is purely advisory. On a cache where bPurgeable is false, SQLite will +** never invoke xUnpin() except to deliberately delete a page. +** In other words, a cache created with bPurgeable set to false will +** never contain any unpinned pages. +** +** The xCachesize() method may be called at any time by SQLite to set the +** suggested maximum cache-size (number of pages stored by) the cache +** instance passed as the first argument. This is the value configured using +** the SQLite "[PRAGMA cache_size]" command. As with the bPurgeable parameter, +** the implementation is not required to do anything with this +** value; it is advisory only. +** +** The xPagecount() method should return the number of pages currently +** stored in the cache. +** +** The xFetch() method is used to fetch a page and return a pointer to it. +** A 'page', in this context, is a buffer of szPage bytes aligned at an +** 8-byte boundary. The page to be fetched is determined by the key. The +** mimimum key value is 1. After it has been retrieved using xFetch, the page +** is considered to be "pinned". +** +** If the requested page is already in the page cache, then the page cache +** implementation must return a pointer to the page buffer with its content +** intact. If the requested page is not already in the cache, then the +** behavior of the cache implementation is determined by the value of the +** createFlag parameter passed to xFetch, according to the following table: +** +** +**
createFlag Behaviour when page is not already in cache +**
0 Do not allocate a new page. Return NULL. +**
1 Allocate a new page if it easy and convenient to do so. +** Otherwise return NULL. +**
2 Make every effort to allocate a new page. Only return +** NULL if allocating a new page is effectively impossible. +**
+** +** SQLite will normally invoke xFetch() with a createFlag of 0 or 1. If +** a call to xFetch() with createFlag==1 returns NULL, then SQLite will +** attempt to unpin one or more cache pages by spilling the content of +** pinned pages to disk and synching the operating system disk cache. After +** attempting to unpin pages, the xFetch() method will be invoked again with +** a createFlag of 2. +** +** xUnpin() is called by SQLite with a pointer to a currently pinned page +** as its second argument. If the third parameter, discard, is non-zero, +** then the page should be evicted from the cache. In this case SQLite +** assumes that the next time the page is retrieved from the cache using +** the xFetch() method, it will be zeroed. If the discard parameter is +** zero, then the page is considered to be unpinned. The cache implementation +** may choose to evict unpinned pages at any time. +** +** The cache is not required to perform any reference counting. A single +** call to xUnpin() unpins the page regardless of the number of prior calls +** to xFetch(). +** +** The xRekey() method is used to change the key value associated with the +** page passed as the second argument from oldKey to newKey. If the cache +** previously contains an entry associated with newKey, it should be +** discarded. Any prior cache entry associated with newKey is guaranteed not +** to be pinned. +** +** When SQLite calls the xTruncate() method, the cache must discard all +** existing cache entries with page numbers (keys) greater than or equal +** to the value of the iLimit parameter passed to xTruncate(). If any +** of these pages are pinned, they are implicitly unpinned, meaning that +** they can be safely discarded. +** +** The xDestroy() method is used to delete a cache allocated by xCreate(). +** All resources associated with the specified cache should be freed. After +** calling the xDestroy() method, SQLite considers the [sqlite3_pcache*] +** handle invalid, and will not use it with any other sqlite3_pcache_methods +** functions. +*/ +typedef struct sqlite3_pcache_methods sqlite3_pcache_methods; +struct sqlite3_pcache_methods { + void *pArg; + int (*xInit)(void*); + void (*xShutdown)(void*); + sqlite3_pcache *(*xCreate)(int szPage, int bPurgeable); + void (*xCachesize)(sqlite3_pcache*, int nCachesize); + int (*xPagecount)(sqlite3_pcache*); + void *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag); + void (*xUnpin)(sqlite3_pcache*, void*, int discard); + void (*xRekey)(sqlite3_pcache*, void*, unsigned oldKey, unsigned newKey); + void (*xTruncate)(sqlite3_pcache*, unsigned iLimit); + void (*xDestroy)(sqlite3_pcache*); +}; + +/* +** CAPI3REF: Online Backup Object +** EXPERIMENTAL +** +** The sqlite3_backup object records state information about an ongoing +** online backup operation. The sqlite3_backup object is created by +** a call to [sqlite3_backup_init()] and is destroyed by a call to +** [sqlite3_backup_finish()]. +** +** See Also: [Using the SQLite Online Backup API] +*/ +typedef struct sqlite3_backup sqlite3_backup; + +/* +** CAPI3REF: Online Backup API. +** EXPERIMENTAL +** +** This API is used to overwrite the contents of one database with that +** of another. It is useful either for creating backups of databases or +** for copying in-memory databases to or from persistent files. +** +** See Also: [Using the SQLite Online Backup API] +** +** Exclusive access is required to the destination database for the +** duration of the operation. However the source database is only +** read-locked while it is actually being read, it is not locked +** continuously for the entire operation. Thus, the backup may be +** performed on a live database without preventing other users from +** writing to the database for an extended period of time. +** +** To perform a backup operation: +**
    +**
  1. sqlite3_backup_init() is called once to initialize the +** backup, +**
  2. sqlite3_backup_step() is called one or more times to transfer +** the data between the two databases, and finally +**
  3. sqlite3_backup_finish() is called to release all resources +** associated with the backup operation. +**
+** There should be exactly one call to sqlite3_backup_finish() for each +** successful call to sqlite3_backup_init(). +** +** sqlite3_backup_init() +** +** The first two arguments passed to [sqlite3_backup_init()] are the database +** handle associated with the destination database and the database name +** used to attach the destination database to the handle. The database name +** is "main" for the main database, "temp" for the temporary database, or +** the name specified as part of the [ATTACH] statement if the destination is +** an attached database. The third and fourth arguments passed to +** sqlite3_backup_init() identify the [database connection] +** and database name used +** to access the source database. The values passed for the source and +** destination [database connection] parameters must not be the same. +** +** If an error occurs within sqlite3_backup_init(), then NULL is returned +** and an error code and error message written into the [database connection] +** passed as the first argument. They may be retrieved using the +** [sqlite3_errcode()], [sqlite3_errmsg()], and [sqlite3_errmsg16()] functions. +** Otherwise, if successful, a pointer to an [sqlite3_backup] object is +** returned. This pointer may be used with the sqlite3_backup_step() and +** sqlite3_backup_finish() functions to perform the specified backup +** operation. +** +** sqlite3_backup_step() +** +** Function [sqlite3_backup_step()] is used to copy up to nPage pages between +** the source and destination databases, where nPage is the value of the +** second parameter passed to sqlite3_backup_step(). If nPage is a negative +** value, all remaining source pages are copied. If the required pages are +** succesfully copied, but there are still more pages to copy before the +** backup is complete, it returns [SQLITE_OK]. If no error occured and there +** are no more pages to copy, then [SQLITE_DONE] is returned. If an error +** occurs, then an SQLite error code is returned. As well as [SQLITE_OK] and +** [SQLITE_DONE], a call to sqlite3_backup_step() may return [SQLITE_READONLY], +** [SQLITE_NOMEM], [SQLITE_BUSY], [SQLITE_LOCKED], or an +** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX] extended error code. +** +** As well as the case where the destination database file was opened for +** read-only access, sqlite3_backup_step() may return [SQLITE_READONLY] if +** the destination is an in-memory database with a different page size +** from the source database. +** +** If sqlite3_backup_step() cannot obtain a required file-system lock, then +** the [sqlite3_busy_handler | busy-handler function] +** is invoked (if one is specified). If the +** busy-handler returns non-zero before the lock is available, then +** [SQLITE_BUSY] is returned to the caller. In this case the call to +** sqlite3_backup_step() can be retried later. If the source +** [database connection] +** is being used to write to the source database when sqlite3_backup_step() +** is called, then [SQLITE_LOCKED] is returned immediately. Again, in this +** case the call to sqlite3_backup_step() can be retried later on. If +** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX], [SQLITE_NOMEM], or +** [SQLITE_READONLY] is returned, then +** there is no point in retrying the call to sqlite3_backup_step(). These +** errors are considered fatal. At this point the application must accept +** that the backup operation has failed and pass the backup operation handle +** to the sqlite3_backup_finish() to release associated resources. +** +** Following the first call to sqlite3_backup_step(), an exclusive lock is +** obtained on the destination file. It is not released until either +** sqlite3_backup_finish() is called or the backup operation is complete +** and sqlite3_backup_step() returns [SQLITE_DONE]. Additionally, each time +** a call to sqlite3_backup_step() is made a [shared lock] is obtained on +** the source database file. This lock is released before the +** sqlite3_backup_step() call returns. Because the source database is not +** locked between calls to sqlite3_backup_step(), it may be modified mid-way +** through the backup procedure. If the source database is modified by an +** external process or via a database connection other than the one being +** used by the backup operation, then the backup will be transparently +** restarted by the next call to sqlite3_backup_step(). If the source +** database is modified by the using the same database connection as is used +** by the backup operation, then the backup database is transparently +** updated at the same time. +** +** sqlite3_backup_finish() +** +** Once sqlite3_backup_step() has returned [SQLITE_DONE], or when the +** application wishes to abandon the backup operation, the [sqlite3_backup] +** object should be passed to sqlite3_backup_finish(). This releases all +** resources associated with the backup operation. If sqlite3_backup_step() +** has not yet returned [SQLITE_DONE], then any active write-transaction on the +** destination database is rolled back. The [sqlite3_backup] object is invalid +** and may not be used following a call to sqlite3_backup_finish(). +** +** The value returned by sqlite3_backup_finish is [SQLITE_OK] if no error +** occurred, regardless or whether or not sqlite3_backup_step() was called +** a sufficient number of times to complete the backup operation. Or, if +** an out-of-memory condition or IO error occured during a call to +** sqlite3_backup_step() then [SQLITE_NOMEM] or an +** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX] error code +** is returned. In this case the error code and an error message are +** written to the destination [database connection]. +** +** A return of [SQLITE_BUSY] or [SQLITE_LOCKED] from sqlite3_backup_step() is +** not a permanent error and does not affect the return value of +** sqlite3_backup_finish(). +** +** sqlite3_backup_remaining(), sqlite3_backup_pagecount() +** +** Each call to sqlite3_backup_step() sets two values stored internally +** by an [sqlite3_backup] object. The number of pages still to be backed +** up, which may be queried by sqlite3_backup_remaining(), and the total +** number of pages in the source database file, which may be queried by +** sqlite3_backup_pagecount(). +** +** The values returned by these functions are only updated by +** sqlite3_backup_step(). If the source database is modified during a backup +** operation, then the values are not updated to account for any extra +** pages that need to be updated or the size of the source database file +** changing. +** +** Concurrent Usage of Database Handles +** +** The source [database connection] may be used by the application for other +** purposes while a backup operation is underway or being initialized. +** If SQLite is compiled and configured to support threadsafe database +** connections, then the source database connection may be used concurrently +** from within other threads. +** +** However, the application must guarantee that the destination database +** connection handle is not passed to any other API (by any thread) after +** sqlite3_backup_init() is called and before the corresponding call to +** sqlite3_backup_finish(). Unfortunately SQLite does not currently check +** for this, if the application does use the destination [database connection] +** for some other purpose during a backup operation, things may appear to +** work correctly but in fact be subtly malfunctioning. Use of the +** destination database connection while a backup is in progress might +** also cause a mutex deadlock. +** +** Furthermore, if running in [shared cache mode], the application must +** guarantee that the shared cache used by the destination database +** is not accessed while the backup is running. In practice this means +** that the application must guarantee that the file-system file being +** backed up to is not accessed by any connection within the process, +** not just the specific connection that was passed to sqlite3_backup_init(). +** +** The [sqlite3_backup] object itself is partially threadsafe. Multiple +** threads may safely make multiple concurrent calls to sqlite3_backup_step(). +** However, the sqlite3_backup_remaining() and sqlite3_backup_pagecount() +** APIs are not strictly speaking threadsafe. If they are invoked at the +** same time as another thread is invoking sqlite3_backup_step() it is +** possible that they return invalid values. +*/ +SQLITE_API sqlite3_backup *sqlite3_backup_init( + sqlite3 *pDest, /* Destination database handle */ + const char *zDestName, /* Destination database name */ + sqlite3 *pSource, /* Source database handle */ + const char *zSourceName /* Source database name */ +); +SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage); +SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p); +SQLITE_API int sqlite3_backup_remaining(sqlite3_backup *p); +SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); + +/* +** CAPI3REF: Unlock Notification +** EXPERIMENTAL +** +** When running in shared-cache mode, a database operation may fail with +** an [SQLITE_LOCKED] error if the required locks on the shared-cache or +** individual tables within the shared-cache cannot be obtained. See +** [SQLite Shared-Cache Mode] for a description of shared-cache locking. +** This API may be used to register a callback that SQLite will invoke +** when the connection currently holding the required lock relinquishes it. +** This API is only available if the library was compiled with the +** [SQLITE_ENABLE_UNLOCK_NOTIFY] C-preprocessor symbol defined. +** +** See Also: [Using the SQLite Unlock Notification Feature]. +** +** Shared-cache locks are released when a database connection concludes +** its current transaction, either by committing it or rolling it back. +** +** When a connection (known as the blocked connection) fails to obtain a +** shared-cache lock and SQLITE_LOCKED is returned to the caller, the +** identity of the database connection (the blocking connection) that +** has locked the required resource is stored internally. After an +** application receives an SQLITE_LOCKED error, it may call the +** sqlite3_unlock_notify() method with the blocked connection handle as +** the first argument to register for a callback that will be invoked +** when the blocking connections current transaction is concluded. The +** callback is invoked from within the [sqlite3_step] or [sqlite3_close] +** call that concludes the blocking connections transaction. +** +** If sqlite3_unlock_notify() is called in a multi-threaded application, +** there is a chance that the blocking connection will have already +** concluded its transaction by the time sqlite3_unlock_notify() is invoked. +** If this happens, then the specified callback is invoked immediately, +** from within the call to sqlite3_unlock_notify(). +** +** If the blocked connection is attempting to obtain a write-lock on a +** shared-cache table, and more than one other connection currently holds +** a read-lock on the same table, then SQLite arbitrarily selects one of +** the other connections to use as the blocking connection. +** +** There may be at most one unlock-notify callback registered by a +** blocked connection. If sqlite3_unlock_notify() is called when the +** blocked connection already has a registered unlock-notify callback, +** then the new callback replaces the old. If sqlite3_unlock_notify() is +** called with a NULL pointer as its second argument, then any existing +** unlock-notify callback is cancelled. The blocked connections +** unlock-notify callback may also be canceled by closing the blocked +** connection using [sqlite3_close()]. +** +** The unlock-notify callback is not reentrant. If an application invokes +** any sqlite3_xxx API functions from within an unlock-notify callback, a +** crash or deadlock may be the result. +** +** Unless deadlock is detected (see below), sqlite3_unlock_notify() always +** returns SQLITE_OK. +** +** Callback Invocation Details +** +** When an unlock-notify callback is registered, the application provides a +** single void* pointer that is passed to the callback when it is invoked. +** However, the signature of the callback function allows SQLite to pass +** it an array of void* context pointers. The first argument passed to +** an unlock-notify callback is a pointer to an array of void* pointers, +** and the second is the number of entries in the array. +** +** When a blocking connections transaction is concluded, there may be +** more than one blocked connection that has registered for an unlock-notify +** callback. If two or more such blocked connections have specified the +** same callback function, then instead of invoking the callback function +** multiple times, it is invoked once with the set of void* context pointers +** specified by the blocked connections bundled together into an array. +** This gives the application an opportunity to prioritize any actions +** related to the set of unblocked database connections. +** +** Deadlock Detection +** +** Assuming that after registering for an unlock-notify callback a +** database waits for the callback to be issued before taking any further +** action (a reasonable assumption), then using this API may cause the +** application to deadlock. For example, if connection X is waiting for +** connection Y's transaction to be concluded, and similarly connection +** Y is waiting on connection X's transaction, then neither connection +** will proceed and the system may remain deadlocked indefinitely. +** +** To avoid this scenario, the sqlite3_unlock_notify() performs deadlock +** detection. If a given call to sqlite3_unlock_notify() would put the +** system in a deadlocked state, then SQLITE_LOCKED is returned and no +** unlock-notify callback is registered. The system is said to be in +** a deadlocked state if connection A has registered for an unlock-notify +** callback on the conclusion of connection B's transaction, and connection +** B has itself registered for an unlock-notify callback when connection +** A's transaction is concluded. Indirect deadlock is also detected, so +** the system is also considered to be deadlocked if connection B has +** registered for an unlock-notify callback on the conclusion of connection +** C's transaction, where connection C is waiting on connection A. Any +** number of levels of indirection are allowed. +** +** The "DROP TABLE" Exception +** +** When a call to [sqlite3_step()] returns SQLITE_LOCKED, it is almost +** always appropriate to call sqlite3_unlock_notify(). There is however, +** one exception. When executing a "DROP TABLE" or "DROP INDEX" statement, +** SQLite checks if there are any currently executing SELECT statements +** that belong to the same connection. If there are, SQLITE_LOCKED is +** returned. In this case there is no "blocking connection", so invoking +** sqlite3_unlock_notify() results in the unlock-notify callback being +** invoked immediately. If the application then re-attempts the "DROP TABLE" +** or "DROP INDEX" query, an infinite loop might be the result. +** +** One way around this problem is to check the extended error code returned +** by an sqlite3_step() call. If there is a blocking connection, then the +** extended error code is set to SQLITE_LOCKED_SHAREDCACHE. Otherwise, in +** the special "DROP TABLE/INDEX" case, the extended error code is just +** SQLITE_LOCKED. +*/ +SQLITE_API int sqlite3_unlock_notify( + sqlite3 *pBlocked, /* Waiting connection */ + void (*xNotify)(void **apArg, int nArg), /* Callback function to invoke */ + void *pNotifyArg /* Argument to pass to xNotify */ +); + + +/* +** CAPI3REF: String Comparison +** EXPERIMENTAL +** +** The [sqlite3_strnicmp()] API allows applications and extensions to +** compare the contents of two buffers containing UTF-8 strings in a +** case-indendent fashion, using the same definition of case independence +** that SQLite uses internally when comparing identifiers. +*/ +SQLITE_API int sqlite3_strnicmp(const char *, const char *, int); + +/* +** Undo the hack that converts floating point types to integer for +** builds on processors without floating point support. +*/ +#ifdef SQLITE_OMIT_FLOATING_POINT +# undef double +#endif + +#ifdef __cplusplus +} /* End of the 'extern "C"' block */ +#endif +#endif + diff --git a/3rdParty/include/sqlite3/sqlite3ext.h b/3rdParty/include/sqlite3/sqlite3ext.h new file mode 100644 index 0000000000..5526646646 --- /dev/null +++ b/3rdParty/include/sqlite3/sqlite3ext.h @@ -0,0 +1,380 @@ +/* +** 2006 June 7 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the SQLite interface for use by +** shared libraries that want to be imported as extensions into +** an SQLite instance. Shared libraries that intend to be loaded +** as extensions by SQLite should #include this file instead of +** sqlite3.h. +** +** @(#) $Id: sqlite3ext.h,v 1.25 2008/10/12 00:27:54 shane Exp $ +*/ +#ifndef _SQLITE3EXT_H_ +#define _SQLITE3EXT_H_ +#include "sqlite3.h" + +typedef struct sqlite3_api_routines sqlite3_api_routines; + +/* +** The following structure holds pointers to all of the SQLite API +** routines. +** +** WARNING: In order to maintain backwards compatibility, add new +** interfaces to the end of this structure only. If you insert new +** interfaces in the middle of this structure, then older different +** versions of SQLite will not be able to load each others' shared +** libraries! +*/ +struct sqlite3_api_routines { + void * (*aggregate_context)(sqlite3_context*,int nBytes); + int (*aggregate_count)(sqlite3_context*); + int (*bind_blob)(sqlite3_stmt*,int,const void*,int n,void(*)(void*)); + int (*bind_double)(sqlite3_stmt*,int,double); + int (*bind_int)(sqlite3_stmt*,int,int); + int (*bind_int64)(sqlite3_stmt*,int,sqlite_int64); + int (*bind_null)(sqlite3_stmt*,int); + int (*bind_parameter_count)(sqlite3_stmt*); + int (*bind_parameter_index)(sqlite3_stmt*,const char*zName); + const char * (*bind_parameter_name)(sqlite3_stmt*,int); + int (*bind_text)(sqlite3_stmt*,int,const char*,int n,void(*)(void*)); + int (*bind_text16)(sqlite3_stmt*,int,const void*,int,void(*)(void*)); + int (*bind_value)(sqlite3_stmt*,int,const sqlite3_value*); + int (*busy_handler)(sqlite3*,int(*)(void*,int),void*); + int (*busy_timeout)(sqlite3*,int ms); + int (*changes)(sqlite3*); + int (*close)(sqlite3*); + int (*collation_needed)(sqlite3*,void*,void(*)(void*,sqlite3*,int eTextRep,const char*)); + int (*collation_needed16)(sqlite3*,void*,void(*)(void*,sqlite3*,int eTextRep,const void*)); + const void * (*column_blob)(sqlite3_stmt*,int iCol); + int (*column_bytes)(sqlite3_stmt*,int iCol); + int (*column_bytes16)(sqlite3_stmt*,int iCol); + int (*column_count)(sqlite3_stmt*pStmt); + const char * (*column_database_name)(sqlite3_stmt*,int); + const void * (*column_database_name16)(sqlite3_stmt*,int); + const char * (*column_decltype)(sqlite3_stmt*,int i); + const void * (*column_decltype16)(sqlite3_stmt*,int); + double (*column_double)(sqlite3_stmt*,int iCol); + int (*column_int)(sqlite3_stmt*,int iCol); + sqlite_int64 (*column_int64)(sqlite3_stmt*,int iCol); + const char * (*column_name)(sqlite3_stmt*,int); + const void * (*column_name16)(sqlite3_stmt*,int); + const char * (*column_origin_name)(sqlite3_stmt*,int); + const void * (*column_origin_name16)(sqlite3_stmt*,int); + const char * (*column_table_name)(sqlite3_stmt*,int); + const void * (*column_table_name16)(sqlite3_stmt*,int); + const unsigned char * (*column_text)(sqlite3_stmt*,int iCol); + const void * (*column_text16)(sqlite3_stmt*,int iCol); + int (*column_type)(sqlite3_stmt*,int iCol); + sqlite3_value* (*column_value)(sqlite3_stmt*,int iCol); + void * (*commit_hook)(sqlite3*,int(*)(void*),void*); + int (*complete)(const char*sql); + int (*complete16)(const void*sql); + int (*create_collation)(sqlite3*,const char*,int,void*,int(*)(void*,int,const void*,int,const void*)); + int (*create_collation16)(sqlite3*,const void*,int,void*,int(*)(void*,int,const void*,int,const void*)); + int (*create_function)(sqlite3*,const char*,int,int,void*,void (*xFunc)(sqlite3_context*,int,sqlite3_value**),void (*xStep)(sqlite3_context*,int,sqlite3_value**),void (*xFinal)(sqlite3_context*)); + int (*create_function16)(sqlite3*,const void*,int,int,void*,void (*xFunc)(sqlite3_context*,int,sqlite3_value**),void (*xStep)(sqlite3_context*,int,sqlite3_value**),void (*xFinal)(sqlite3_context*)); + int (*create_module)(sqlite3*,const char*,const sqlite3_module*,void*); + int (*data_count)(sqlite3_stmt*pStmt); + sqlite3 * (*db_handle)(sqlite3_stmt*); + int (*declare_vtab)(sqlite3*,const char*); + int (*enable_shared_cache)(int); + int (*errcode)(sqlite3*db); + const char * (*errmsg)(sqlite3*); + const void * (*errmsg16)(sqlite3*); + int (*exec)(sqlite3*,const char*,sqlite3_callback,void*,char**); + int (*expired)(sqlite3_stmt*); + int (*finalize)(sqlite3_stmt*pStmt); + void (*free)(void*); + void (*free_table)(char**result); + int (*get_autocommit)(sqlite3*); + void * (*get_auxdata)(sqlite3_context*,int); + int (*get_table)(sqlite3*,const char*,char***,int*,int*,char**); + int (*global_recover)(void); + void (*interruptx)(sqlite3*); + sqlite_int64 (*last_insert_rowid)(sqlite3*); + const char * (*libversion)(void); + int (*libversion_number)(void); + void *(*malloc)(int); + char * (*mprintf)(const char*,...); + int (*open)(const char*,sqlite3**); + int (*open16)(const void*,sqlite3**); + int (*prepare)(sqlite3*,const char*,int,sqlite3_stmt**,const char**); + int (*prepare16)(sqlite3*,const void*,int,sqlite3_stmt**,const void**); + void * (*profile)(sqlite3*,void(*)(void*,const char*,sqlite_uint64),void*); + void (*progress_handler)(sqlite3*,int,int(*)(void*),void*); + void *(*realloc)(void*,int); + int (*reset)(sqlite3_stmt*pStmt); + void (*result_blob)(sqlite3_context*,const void*,int,void(*)(void*)); + void (*result_double)(sqlite3_context*,double); + void (*result_error)(sqlite3_context*,const char*,int); + void (*result_error16)(sqlite3_context*,const void*,int); + void (*result_int)(sqlite3_context*,int); + void (*result_int64)(sqlite3_context*,sqlite_int64); + void (*result_null)(sqlite3_context*); + void (*result_text)(sqlite3_context*,const char*,int,void(*)(void*)); + void (*result_text16)(sqlite3_context*,const void*,int,void(*)(void*)); + void (*result_text16be)(sqlite3_context*,const void*,int,void(*)(void*)); + void (*result_text16le)(sqlite3_context*,const void*,int,void(*)(void*)); + void (*result_value)(sqlite3_context*,sqlite3_value*); + void * (*rollback_hook)(sqlite3*,void(*)(void*),void*); + int (*set_authorizer)(sqlite3*,int(*)(void*,int,const char*,const char*,const char*,const char*),void*); + void (*set_auxdata)(sqlite3_context*,int,void*,void (*)(void*)); + char * (*snprintf)(int,char*,const char*,...); + int (*step)(sqlite3_stmt*); + int (*table_column_metadata)(sqlite3*,const char*,const char*,const char*,char const**,char const**,int*,int*,int*); + void (*thread_cleanup)(void); + int (*total_changes)(sqlite3*); + void * (*trace)(sqlite3*,void(*xTrace)(void*,const char*),void*); + int (*transfer_bindings)(sqlite3_stmt*,sqlite3_stmt*); + void * (*update_hook)(sqlite3*,void(*)(void*,int ,char const*,char const*,sqlite_int64),void*); + void * (*user_data)(sqlite3_context*); + const void * (*value_blob)(sqlite3_value*); + int (*value_bytes)(sqlite3_value*); + int (*value_bytes16)(sqlite3_value*); + double (*value_double)(sqlite3_value*); + int (*value_int)(sqlite3_value*); + sqlite_int64 (*value_int64)(sqlite3_value*); + int (*value_numeric_type)(sqlite3_value*); + const unsigned char * (*value_text)(sqlite3_value*); + const void * (*value_text16)(sqlite3_value*); + const void * (*value_text16be)(sqlite3_value*); + const void * (*value_text16le)(sqlite3_value*); + int (*value_type)(sqlite3_value*); + char *(*vmprintf)(const char*,va_list); + /* Added ??? */ + int (*overload_function)(sqlite3*, const char *zFuncName, int nArg); + /* Added by 3.3.13 */ + int (*prepare_v2)(sqlite3*,const char*,int,sqlite3_stmt**,const char**); + int (*prepare16_v2)(sqlite3*,const void*,int,sqlite3_stmt**,const void**); + int (*clear_bindings)(sqlite3_stmt*); + /* Added by 3.4.1 */ + int (*create_module_v2)(sqlite3*,const char*,const sqlite3_module*,void*,void (*xDestroy)(void *)); + /* Added by 3.5.0 */ + int (*bind_zeroblob)(sqlite3_stmt*,int,int); + int (*blob_bytes)(sqlite3_blob*); + int (*blob_close)(sqlite3_blob*); + int (*blob_open)(sqlite3*,const char*,const char*,const char*,sqlite3_int64,int,sqlite3_blob**); + int (*blob_read)(sqlite3_blob*,void*,int,int); + int (*blob_write)(sqlite3_blob*,const void*,int,int); + int (*create_collation_v2)(sqlite3*,const char*,int,void*,int(*)(void*,int,const void*,int,const void*),void(*)(void*)); + int (*file_control)(sqlite3*,const char*,int,void*); + sqlite3_int64 (*memory_highwater)(int); + sqlite3_int64 (*memory_used)(void); + sqlite3_mutex *(*mutex_alloc)(int); + void (*mutex_enter)(sqlite3_mutex*); + void (*mutex_free)(sqlite3_mutex*); + void (*mutex_leave)(sqlite3_mutex*); + int (*mutex_try)(sqlite3_mutex*); + int (*open_v2)(const char*,sqlite3**,int,const char*); + int (*release_memory)(int); + void (*result_error_nomem)(sqlite3_context*); + void (*result_error_toobig)(sqlite3_context*); + int (*sleep)(int); + void (*soft_heap_limit)(int); + sqlite3_vfs *(*vfs_find)(const char*); + int (*vfs_register)(sqlite3_vfs*,int); + int (*vfs_unregister)(sqlite3_vfs*); + int (*xthreadsafe)(void); + void (*result_zeroblob)(sqlite3_context*,int); + void (*result_error_code)(sqlite3_context*,int); + int (*test_control)(int, ...); + void (*randomness)(int,void*); + sqlite3 *(*context_db_handle)(sqlite3_context*); + int (*extended_result_codes)(sqlite3*,int); + int (*limit)(sqlite3*,int,int); + sqlite3_stmt *(*next_stmt)(sqlite3*,sqlite3_stmt*); + const char *(*sql)(sqlite3_stmt*); + int (*status)(int,int*,int*,int); +}; + +/* +** The following macros redefine the API routines so that they are +** redirected throught the global sqlite3_api structure. +** +** This header file is also used by the loadext.c source file +** (part of the main SQLite library - not an extension) so that +** it can get access to the sqlite3_api_routines structure +** definition. But the main library does not want to redefine +** the API. So the redefinition macros are only valid if the +** SQLITE_CORE macros is undefined. +*/ +#ifndef SQLITE_CORE +#define sqlite3_aggregate_context sqlite3_api->aggregate_context +#ifndef SQLITE_OMIT_DEPRECATED +#define sqlite3_aggregate_count sqlite3_api->aggregate_count +#endif +#define sqlite3_bind_blob sqlite3_api->bind_blob +#define sqlite3_bind_double sqlite3_api->bind_double +#define sqlite3_bind_int sqlite3_api->bind_int +#define sqlite3_bind_int64 sqlite3_api->bind_int64 +#define sqlite3_bind_null sqlite3_api->bind_null +#define sqlite3_bind_parameter_count sqlite3_api->bind_parameter_count +#define sqlite3_bind_parameter_index sqlite3_api->bind_parameter_index +#define sqlite3_bind_parameter_name sqlite3_api->bind_parameter_name +#define sqlite3_bind_text sqlite3_api->bind_text +#define sqlite3_bind_text16 sqlite3_api->bind_text16 +#define sqlite3_bind_value sqlite3_api->bind_value +#define sqlite3_busy_handler sqlite3_api->busy_handler +#define sqlite3_busy_timeout sqlite3_api->busy_timeout +#define sqlite3_changes sqlite3_api->changes +#define sqlite3_close sqlite3_api->close +#define sqlite3_collation_needed sqlite3_api->collation_needed +#define sqlite3_collation_needed16 sqlite3_api->collation_needed16 +#define sqlite3_column_blob sqlite3_api->column_blob +#define sqlite3_column_bytes sqlite3_api->column_bytes +#define sqlite3_column_bytes16 sqlite3_api->column_bytes16 +#define sqlite3_column_count sqlite3_api->column_count +#define sqlite3_column_database_name sqlite3_api->column_database_name +#define sqlite3_column_database_name16 sqlite3_api->column_database_name16 +#define sqlite3_column_decltype sqlite3_api->column_decltype +#define sqlite3_column_decltype16 sqlite3_api->column_decltype16 +#define sqlite3_column_double sqlite3_api->column_double +#define sqlite3_column_int sqlite3_api->column_int +#define sqlite3_column_int64 sqlite3_api->column_int64 +#define sqlite3_column_name sqlite3_api->column_name +#define sqlite3_column_name16 sqlite3_api->column_name16 +#define sqlite3_column_origin_name sqlite3_api->column_origin_name +#define sqlite3_column_origin_name16 sqlite3_api->column_origin_name16 +#define sqlite3_column_table_name sqlite3_api->column_table_name +#define sqlite3_column_table_name16 sqlite3_api->column_table_name16 +#define sqlite3_column_text sqlite3_api->column_text +#define sqlite3_column_text16 sqlite3_api->column_text16 +#define sqlite3_column_type sqlite3_api->column_type +#define sqlite3_column_value sqlite3_api->column_value +#define sqlite3_commit_hook sqlite3_api->commit_hook +#define sqlite3_complete sqlite3_api->complete +#define sqlite3_complete16 sqlite3_api->complete16 +#define sqlite3_create_collation sqlite3_api->create_collation +#define sqlite3_create_collation16 sqlite3_api->create_collation16 +#define sqlite3_create_function sqlite3_api->create_function +#define sqlite3_create_function16 sqlite3_api->create_function16 +#define sqlite3_create_module sqlite3_api->create_module +#define sqlite3_create_module_v2 sqlite3_api->create_module_v2 +#define sqlite3_data_count sqlite3_api->data_count +#define sqlite3_db_handle sqlite3_api->db_handle +#define sqlite3_declare_vtab sqlite3_api->declare_vtab +#define sqlite3_enable_shared_cache sqlite3_api->enable_shared_cache +#define sqlite3_errcode sqlite3_api->errcode +#define sqlite3_errmsg sqlite3_api->errmsg +#define sqlite3_errmsg16 sqlite3_api->errmsg16 +#define sqlite3_exec sqlite3_api->exec +#ifndef SQLITE_OMIT_DEPRECATED +#define sqlite3_expired sqlite3_api->expired +#endif +#define sqlite3_finalize sqlite3_api->finalize +#define sqlite3_free sqlite3_api->free +#define sqlite3_free_table sqlite3_api->free_table +#define sqlite3_get_autocommit sqlite3_api->get_autocommit +#define sqlite3_get_auxdata sqlite3_api->get_auxdata +#define sqlite3_get_table sqlite3_api->get_table +#ifndef SQLITE_OMIT_DEPRECATED +#define sqlite3_global_recover sqlite3_api->global_recover +#endif +#define sqlite3_interrupt sqlite3_api->interruptx +#define sqlite3_last_insert_rowid sqlite3_api->last_insert_rowid +#define sqlite3_libversion sqlite3_api->libversion +#define sqlite3_libversion_number sqlite3_api->libversion_number +#define sqlite3_malloc sqlite3_api->malloc +#define sqlite3_mprintf sqlite3_api->mprintf +#define sqlite3_open sqlite3_api->open +#define sqlite3_open16 sqlite3_api->open16 +#define sqlite3_prepare sqlite3_api->prepare +#define sqlite3_prepare16 sqlite3_api->prepare16 +#define sqlite3_prepare_v2 sqlite3_api->prepare_v2 +#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2 +#define sqlite3_profile sqlite3_api->profile +#define sqlite3_progress_handler sqlite3_api->progress_handler +#define sqlite3_realloc sqlite3_api->realloc +#define sqlite3_reset sqlite3_api->reset +#define sqlite3_result_blob sqlite3_api->result_blob +#define sqlite3_result_double sqlite3_api->result_double +#define sqlite3_result_error sqlite3_api->result_error +#define sqlite3_result_error16 sqlite3_api->result_error16 +#define sqlite3_result_int sqlite3_api->result_int +#define sqlite3_result_int64 sqlite3_api->result_int64 +#define sqlite3_result_null sqlite3_api->result_null +#define sqlite3_result_text sqlite3_api->result_text +#define sqlite3_result_text16 sqlite3_api->result_text16 +#define sqlite3_result_text16be sqlite3_api->result_text16be +#define sqlite3_result_text16le sqlite3_api->result_text16le +#define sqlite3_result_value sqlite3_api->result_value +#define sqlite3_rollback_hook sqlite3_api->rollback_hook +#define sqlite3_set_authorizer sqlite3_api->set_authorizer +#define sqlite3_set_auxdata sqlite3_api->set_auxdata +#define sqlite3_snprintf sqlite3_api->snprintf +#define sqlite3_step sqlite3_api->step +#define sqlite3_table_column_metadata sqlite3_api->table_column_metadata +#define sqlite3_thread_cleanup sqlite3_api->thread_cleanup +#define sqlite3_total_changes sqlite3_api->total_changes +#define sqlite3_trace sqlite3_api->trace +#ifndef SQLITE_OMIT_DEPRECATED +#define sqlite3_transfer_bindings sqlite3_api->transfer_bindings +#endif +#define sqlite3_update_hook sqlite3_api->update_hook +#define sqlite3_user_data sqlite3_api->user_data +#define sqlite3_value_blob sqlite3_api->value_blob +#define sqlite3_value_bytes sqlite3_api->value_bytes +#define sqlite3_value_bytes16 sqlite3_api->value_bytes16 +#define sqlite3_value_double sqlite3_api->value_double +#define sqlite3_value_int sqlite3_api->value_int +#define sqlite3_value_int64 sqlite3_api->value_int64 +#define sqlite3_value_numeric_type sqlite3_api->value_numeric_type +#define sqlite3_value_text sqlite3_api->value_text +#define sqlite3_value_text16 sqlite3_api->value_text16 +#define sqlite3_value_text16be sqlite3_api->value_text16be +#define sqlite3_value_text16le sqlite3_api->value_text16le +#define sqlite3_value_type sqlite3_api->value_type +#define sqlite3_vmprintf sqlite3_api->vmprintf +#define sqlite3_overload_function sqlite3_api->overload_function +#define sqlite3_prepare_v2 sqlite3_api->prepare_v2 +#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2 +#define sqlite3_clear_bindings sqlite3_api->clear_bindings +#define sqlite3_bind_zeroblob sqlite3_api->bind_zeroblob +#define sqlite3_blob_bytes sqlite3_api->blob_bytes +#define sqlite3_blob_close sqlite3_api->blob_close +#define sqlite3_blob_open sqlite3_api->blob_open +#define sqlite3_blob_read sqlite3_api->blob_read +#define sqlite3_blob_write sqlite3_api->blob_write +#define sqlite3_create_collation_v2 sqlite3_api->create_collation_v2 +#define sqlite3_file_control sqlite3_api->file_control +#define sqlite3_memory_highwater sqlite3_api->memory_highwater +#define sqlite3_memory_used sqlite3_api->memory_used +#define sqlite3_mutex_alloc sqlite3_api->mutex_alloc +#define sqlite3_mutex_enter sqlite3_api->mutex_enter +#define sqlite3_mutex_free sqlite3_api->mutex_free +#define sqlite3_mutex_leave sqlite3_api->mutex_leave +#define sqlite3_mutex_try sqlite3_api->mutex_try +#define sqlite3_open_v2 sqlite3_api->open_v2 +#define sqlite3_release_memory sqlite3_api->release_memory +#define sqlite3_result_error_nomem sqlite3_api->result_error_nomem +#define sqlite3_result_error_toobig sqlite3_api->result_error_toobig +#define sqlite3_sleep sqlite3_api->sleep +#define sqlite3_soft_heap_limit sqlite3_api->soft_heap_limit +#define sqlite3_vfs_find sqlite3_api->vfs_find +#define sqlite3_vfs_register sqlite3_api->vfs_register +#define sqlite3_vfs_unregister sqlite3_api->vfs_unregister +#define sqlite3_threadsafe sqlite3_api->xthreadsafe +#define sqlite3_result_zeroblob sqlite3_api->result_zeroblob +#define sqlite3_result_error_code sqlite3_api->result_error_code +#define sqlite3_test_control sqlite3_api->test_control +#define sqlite3_randomness sqlite3_api->randomness +#define sqlite3_context_db_handle sqlite3_api->context_db_handle +#define sqlite3_extended_result_codes sqlite3_api->extended_result_codes +#define sqlite3_limit sqlite3_api->limit +#define sqlite3_next_stmt sqlite3_api->next_stmt +#define sqlite3_sql sqlite3_api->sql +#define sqlite3_status sqlite3_api->status +#endif /* SQLITE_CORE */ + +#define SQLITE_EXTENSION_INIT1 const sqlite3_api_routines *sqlite3_api = 0; +#define SQLITE_EXTENSION_INIT2(v) sqlite3_api = v; + +#endif /* _SQLITE3EXT_H_ */ diff --git a/3rdParty/lib/libcppunit.dll.a b/3rdParty/lib/libcppunit.dll.a new file mode 100644 index 0000000000..695ecc0e67 Binary files /dev/null and b/3rdParty/lib/libcppunit.dll.a differ diff --git a/3rdParty/lib/libsqlite3.dll.a b/3rdParty/lib/libsqlite3.dll.a new file mode 100644 index 0000000000..5ab589e604 Binary files /dev/null and b/3rdParty/lib/libsqlite3.dll.a differ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000..0bc3e9c868 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,271 @@ +# Top-Level CmakeLists.txt +IF(APPLE OR WIN32) + # BundleUtilities.cmake from 2.8.3 required on Mac and ?Windows? + cmake_minimum_required(VERSION 2.8.3) +ELSE() + cmake_minimum_required(VERSION 2.8.0) +ENDIF() +PROJECT( RTAB-Map ) +SET(PROJECT_PREFIX rtabmap) + +####### local cmake modules ####### +SET(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake_modules") + +####################### +# VERSION +####################### +SET(PROJECT_VERSION "0.2.7") + +STRING(REGEX MATCHALL "[0-9]" PROJECT_VERSION_PARTS "${PROJECT_VERSION}") + +LIST(GET PROJECT_VERSION_PARTS 0 PROJECT_VERSION_MAJOR) +LIST(GET PROJECT_VERSION_PARTS 1 PROJECT_VERSION_MINOR) +LIST(GET PROJECT_VERSION_PARTS 2 PROJECT_VERSION_PATCH) + +SET(PROJECT_SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") + +CONFIGURE_FILE(Version.h.in ${PROJECT_SOURCE_DIR}/corelib/include/${PROJECT_PREFIX}/core/Version.h) + +####### COMPILATION PARAMS ####### +# In case of Makefiles if the user does not setup CMAKE_BUILD_TYPE, assume it's Release: +IF(${CMAKE_GENERATOR} MATCHES ".*Makefiles") + IF("${CMAKE_BUILD_TYPE}" STREQUAL "") + set(CMAKE_BUILD_TYPE Release) + ENDIF("${CMAKE_BUILD_TYPE}" STREQUAL "") +ENDIF(${CMAKE_GENERATOR} MATCHES ".*Makefiles") + +SET(CMAKE_DEBUG_POSTFIX "d") + +ADD_DEFINITIONS( "-Wall" ) +IF(WIN32 AND NOT MINGW) + ADD_DEFINITIONS("-wd4100 -wd4512 -wd4548 -wd4619 -wd4625 -wd4626 -wd4668 -wd4710 -wd4820") +ELSE () + ADD_DEFINITIONS("-Wno-unknown-pragmas") +ENDIF() + +IF(MINGW) + # Hide the --enable-auto-import warning + SET(CMAKE_EXE_LINKER_FLAGS "-enable-auto-import") + SET(CMAKE_MODULE_LINKER_FLAGS "-enable-auto-import") + SET(CMAKE_SHARED_LINKER_FLAGS "-enable-auto-import") +ENDIF(MINGW) + +# GCC 4 required +IF(UNIX OR MINGW) + EXEC_PROGRAM( gcc ARGS "-dumpversion" OUTPUT_VARIABLE GCC_VERSION ) + IF(GCC_VERSION VERSION_LESS "4.0.0") + MESSAGE(FATAL_ERROR "GCC ${GCC_VERSION} found, but version 4.x.x minimum is required") + ENDIF(GCC_VERSION VERSION_LESS "4.0.0") +ENDIF(UNIX OR MINGW) + +#The CDT Error Parser cannot handle error messages that span +#more than one line, which is the default gcc behavior. +#In order to force gcc to generate single line error messages with no line wrapping +IF(CMAKE_COMPILER_IS_GNUCC) + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fmessage-length=0") +ENDIF(CMAKE_COMPILER_IS_GNUCC) +IF(CMAKE_COMPILER_IS_GNUCXX) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmessage-length=0") +ENDIF(CMAKE_COMPILER_IS_GNUCXX) + +# [Eclipse] Automatic Discovery of Include directories (Optional, but handy) +#SET(CMAKE_VERBOSE_MAKEFILE ON) + +#Find libraries included with our project first +IF(WIN32) + SET(CMAKE_INCLUDE_PATH "${PROJECT_SOURCE_DIR}/3rdParty/include;${CMAKE_INCLUDE_PATH}") + SET(CMAKE_LIBRARY_PATH "${PROJECT_SOURCE_DIR}/3rdParty/lib;${CMAKE_LIBRARY_PATH}") +ENDIF(WIN32) + +####### Build libraries as shared or static ####### +OPTION(BUILD_SHARED_LIBS "Set to OFF to build static libraries" ON) + +####### SET RPATH ######### +# When RPATH is activated (supported on most UNIX systems), +# the user doesn't need to change LD_LIBRARY_PATH + +# use, i.e. don't skip the full RPATH for the build tree +SET(CMAKE_SKIP_BUILD_RPATH FALSE) + +# when building, don't use the install RPATH already +# (but later on when installing) +SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) + +# the RPATH to be used when installing +SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") + +# add the automatically determined parts of the RPATH +# which point to directories outside the build tree to the install RPATH +SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + +####### OUTPUT DIR ####### +SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin) +SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin) +SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib) + + +####### DEPENDENCIES ####### +FIND_PACKAGE(UtiLite REQUIRED) +FIND_PACKAGE(OpenCV REQUIRED) +FIND_PACKAGE(Sqlite3 REQUIRED) +# If Qt is here, the GUI will be built +FIND_PACKAGE(Qt4 COMPONENTS QtCore QtGui QtSvg) +# If CppUnit is here, the test will be built +FIND_PACKAGE(CppUnit) + +####### OSX BUNDLE CMAKE_INSTALL_PREFIX ####### +OPTION(BUILD_AS_BUNDLE "Set to ON to build as bundle (DragNDrop)" OFF) +IF(APPLE AND BUILD_AS_BUNDLE) + IF(QT4_FOUND AND QT_QTCORE_FOUND AND QT_QTGUI_FOUND) + + #Force to Off, easiest to package into the bundle + SET( BUILD_SHARED_LIBS OFF ) + + # Required when packaging, and set CMAKE_INSTALL_PREFIX to "/". + SET(CPACK_SET_DESTDIR TRUE) + + SET(CMAKE_BUNDLE_NAME + "${PROJECT_NAME} ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") + SET(CMAKE_BUNDLE_LOCATION "${CMAKE_INSTALL_PREFIX}") + + # make sure CMAKE_INSTALL_PREFIX ends in / + STRING(LENGTH "${CMAKE_INSTALL_PREFIX}" LEN) + MATH(EXPR LEN "${LEN} -1" ) + STRING(SUBSTRING "${CMAKE_INSTALL_PREFIX}" ${LEN} 1 ENDCH) + IF(NOT "${ENDCH}" STREQUAL "/") + SET(CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/") + ENDIF(NOT "${ENDCH}" STREQUAL "/") + SET(CMAKE_INSTALL_PREFIX + "${CMAKE_INSTALL_PREFIX}${CMAKE_BUNDLE_NAME}.app/Contents") + ELSE() + + #If Qt is not here, no need to build a bundle + SET(BUILD_AS_BUNDLE OFF) + ENDIF() +ENDIF(APPLE AND BUILD_AS_BUNDLE) + + +####### SOURCES (Projects) ####### +ADD_SUBDIRECTORY( corelib ) +INCLUDE_DIRECTORIES(corelib/include/) + +IF(QT4_FOUND AND QT_QTCORE_FOUND AND QT_QTGUI_FOUND) + ADD_SUBDIRECTORY( guilib ) + INCLUDE_DIRECTORIES(guilib/include/) + ADD_SUBDIRECTORY( ${PROJECT_PREFIX} ) +ELSE() + MESSAGE(STATUS "[WARNING] Qt4 not found, the GUI lib and the stand-alone application will not be compiled...") +ENDIF() + +####################### +# Uninstall target, for "make uninstall" +####################### +CONFIGURE_FILE( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + +ADD_CUSTOM_TARGET(uninstall + "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") + + +####################### +# CPACK (Packaging) +####################### +INCLUDE(InstallRequiredSystemLibraries) + +SET(CPACK_PACKAGE_NAME "${PROJECT_NAME}") +SET(CPACK_PACKAGE_VENDOR "${PROJECT_NAME} project") +SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "CTAB-MAP is a Real-Time Appearance-Based Mapping approach.") +#SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/Description.txt") +#SET(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/README.txt") +SET(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}") +SET(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}") +SET(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}") +#SET(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}") +#SET(CPACK_PACKAGE_EXECUTABLES "") +#SET(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") +SET(CPACK_PACKAGE_CONTACT "matlabbe@gmail.com") + +set(CPACK_SOURCE_IGNORE_FILES + "\\\\.svn/" + "${PROJECT_SOURCE_DIR}/build/[a-zA-Z0-9_]+" + "~$" + "${PROJECT_SOURCE_DIR}/bin/.*${PROJECT_PREFIX}" + "${PROJECT_SOURCE_DIR}/bin/.*${PROJECT_NAME}" + "${PROJECT_SOURCE_DIR}/bin/.*[tT]est" + "${PROJECT_SOURCE_DIR}/bin/.*[eE]xample" + "\\\\.DS_Store" +) + +# Share files +INSTALL(FILES + ${PROJECT_SOURCE_DIR}/Matlab/ShowLogs/ShowLogs.m + ${PROJECT_SOURCE_DIR}/Matlab/ShowLogs/importfile.m + DESTINATION share/${PROJECT_PREFIX} + COMPONENT runtime) + +IF(WIN32) + SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/COPYING.txt") + + SET(CPACK_GENERATOR "NSIS") + SET(CPACK_SOURCE_GENERATOR "ZIP") + SET(CPACK_NSIS_PACKAGE_NAME "${PROJECT_NAME} ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") + SET(ICON_PATH "${PROJECT_SOURCE_DIR}/${PROJECT_PREFIX}/src/${PROJECT_NAME}.ico") + SET(CPACK_NSIS_MUI_ICON ${ICON_PATH}) + SET(CPACK_NSIS_MUI_UNIICON ${ICON_PATH}) + #SET(CPACK_PACKAGE_ICON ${ICON_PATH}) + #SET(CPACK_NSIS_INSTALLED_ICON_NAME ${ICON_PATH}) + #SET(CPACK_NSIS_HELP_LINK "http:\\\\\\\\www.${PROJECT_PREFIX}.googlecode.com") + #SET(CPACK_NSIS_URL_INFO_ABOUT "http:\\\\\\\\www.${PROJECT_PREFIX}.googlecode.com") + SET(CPACK_NSIS_DISPLAY_NAME "${PROJECT_NAME}") + SET(CPACK_NSIS_CONTACT ${CPACK_PACKAGE_CONTACT}) + # Set the icon used for the Windows "Add or Remove Programs" tool. + SET(CPACK_NSIS_INSTALLED_ICON_NAME bin\\\\${PROJECT_NAME}.exe) + # Desktop link ("executableName" "linkName") + SET(CPACK_PACKAGE_EXECUTABLES "${PROJECT_NAME}" "${PROJECT_NAME}" ${CPACK_PACKAGE_EXECUTABLES}) + SET(CPACK_CREATE_DESKTOP_LINKS "${PROJECT_NAME}" ${CPACK_CREATE_DESKTOP_LINKS}) + SET(CPACK_NSIS_MODIFY_PATH ON) + +ELSEIF(APPLE) + # The project is created as a bundle over the main app (see ./${PROJECT_PREFIX}/src). + # Here we package only this bundle. Note that we set + # CMAKE_INSTALL_PREFIX to "/" when packaging... + IF(BUILD_AS_BUNDLE) + SET(CPACK_GENERATOR "DragNDrop") + ELSE() + SET(CPACK_GENERATOR "PackageMaker;TBZ2") + ENDIF() + + SET(CPACK_SOURCE_GENERATOR "TBZ2") + + SET(CPACK_PACKAGE_ICON "${PROJECT_SOURCE_DIR}/${PROJECT_PREFIX}/src/${PROJECT_NAME}.icns") + +ELSE() + SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/COPYING.txt") + SET(CPACK_GENERATOR "DEB;TBZ2") + SET(CPACK_SOURCE_GENERATOR "TBZ2") + + # Extern : UtiLite (>= 0.2) http://code.google.com/p/utilite/, OpenCV (>= 2.2) http://opencv.willowgarage.com/wiki/ + # ex. SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.3.1-6), libgcc1 (>= 1:3.4.2-12)") + # have a look at GET_PROPERTY(result GLOBAL ENABLED_FEATURES), this returns the successful FIND_PACKAGE() calls, maybe this can help + SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.4), libstdc++6 (>= 4.4), libgcc1 (>= 4.0), libsqlite3-0 (>= 3.6)") # Most have + SET(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libqt4-dev (>= 4.6)") #Qt + SET(CPACK_DEBIAN_PACKAGE_SUGGESTS "") + + SET(CPACK_DEBIAN_PACKAGE_SECTION "devel") + SET(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") + +ENDIF() + +INCLUDE(CPack) + +MESSAGE(STATUS "--------------------------------------------") +MESSAGE(STATUS "Info :") +MESSAGE(STATUS " CMAKE_INSTALL_PREFIX = ${CMAKE_INSTALL_PREFIX}") +MESSAGE(STATUS " CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}") +MESSAGE(STATUS " BUILD_SHARED_LIBS = ${BUILD_SHARED_LIBS}") +IF(APPLE) +MESSAGE(STATUS " BUILD_AS_BUNDLE = ${BUILD_AS_BUNDLE}") +ENDIF(APPLE) +MESSAGE(STATUS "--------------------------------------------") diff --git a/COPYING.txt b/COPYING.txt new file mode 100644 index 0000000000..94a9ed024d --- /dev/null +++ b/COPYING.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Matlab/DumpMemorySign.txt b/Matlab/DumpMemorySign.txt new file mode 100644 index 0000000000..75ac829bef --- /dev/null +++ b/Matlab/DumpMemorySign.txt @@ -0,0 +1,86 @@ +SignatureID WordsID... +-1 4 5 21 22 24 31 32 33 35 46 48 49 58 60 63 72 73 78 83 84 103 105 114 120 124 133 136 141 162 179 182 188 189 202 204 211 217 218 227 232 241 247 249 260 268 271 287 289 301 304 318 320 324 338 340 345 346 349 358 366 368 370 371 381 395 407 408 414 415 416 424 439 442 447 502 504 508 509 527 539 540 550 553 558 568 572 583 588 598 606 617 624 625 638 652 653 667 674 693 694 699 702 738 755 770 782 784 793 816 818 819 832 836 842 846 847 858 867 868 923 939 954 955 965 987 1027 1030 1034 1083 1087 1097 1098 1177 1187 1199 1200 1214 1241 1258 1259 1270 1303 1339 1351 1388 1451 1501 1515 1526 1630 1679 1680 1771 1841 1868 2014 2243 2400 2494 2591 2957 3075 3079 3194 +1 1 2 2 3 4 4 4 5 6 7 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 24 24 25 26 27 28 29 30 31 31 32 33 34 34 35 35 36 37 38 39 40 41 42 43 44 44 45 46 47 48 49 49 50 51 52 53 53 54 55 56 57 58 59 59 60 60 60 61 62 63 64 65 66 67 68 69 70 70 71 72 73 74 75 76 77 78 79 80 81 82 83 83 83 84 85 86 86 87 88 89 90 91 92 93 94 95 96 97 98 99 99 100 101 102 103 104 +2 4 4 4 4 4 4 5 7 7 7 12 14 18 19 21 22 26 28 29 29 31 32 33 35 37 48 49 49 51 51 53 54 55 56 56 57 58 58 59 60 60 60 63 65 66 67 70 73 75 77 77 77 78 78 82 84 84 84 84 92 94 94 96 96 99 103 105 105 106 107 108 109 110 111 112 113 114 115 116 116 117 118 118 119 120 120 121 122 123 124 125 126 127 128 129 130 131 132 133 133 133 134 135 136 137 138 139 140 141 141 142 143 144 145 146 147 148 149 150 151 151 152 152 153 154 155 156 156 157 158 159 160 161 161 162 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 189 189 190 191 192 193 194 195 196 197 198 199 200 201 202 202 203 204 204 205 +3 4 4 5 12 12 19 21 22 23 28 28 29 32 32 33 33 44 46 48 48 49 52 53 55 56 58 59 60 60 65 65 67 71 72 72 73 78 84 96 96 97 103 105 105 123 124 124 133 136 136 136 139 139 141 141 141 142 143 144 148 162 167 167 182 188 189 189 202 206 207 208 209 210 210 211 212 213 214 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 247 248 249 249 250 251 252 253 254 255 255 256 257 258 259 260 261 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 287 288 289 290 291 292 293 294 295 296 296 297 298 298 299 300 301 302 303 303 304 305 306 306 307 307 308 309 310 311 +4 4 5 18 21 21 22 24 24 32 33 35 42 44 46 46 46 46 49 65 67 68 68 70 70 72 72 72 83 84 95 96 96 103 120 130 135 136 136 136 136 139 142 143 143 153 156 162 177 179 179 182 189 189 189 189 189 189 202 202 204 217 217 218 218 227 227 228 228 230 230 232 246 247 249 249 255 268 271 271 275 276 276 284 287 290 312 313 314 315 316 317 318 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 338 339 340 341 342 343 344 345 346 347 348 349 349 350 351 352 352 353 354 354 355 356 357 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 408 409 410 411 412 413 +5 4 5 24 25 31 32 33 42 45 45 48 48 67 70 84 105 114 120 120 120 130 133 179 189 202 202 217 217 217 217 217 217 217 217 232 268 270 271 318 324 338 345 346 349 349 354 355 356 362 366 366 407 414 414 415 416 417 418 418 419 420 421 422 423 423 424 424 425 425 426 427 428 429 429 430 431 432 433 433 434 435 436 436 437 438 439 439 440 441 442 443 444 445 446 447 448 449 450 451 451 452 453 454 455 456 457 458 459 460 +6 4 4 4 4 21 22 24 24 25 31 31 31 31 31 31 32 33 45 48 64 64 72 72 72 84 95 105 114 114 114 120 122 130 136 156 189 189 189 189 189 189 202 202 208 213 217 217 217 227 232 243 271 280 287 312 320 320 320 324 324 340 345 345 358 366 366 366 366 407 414 416 416 416 416 421 423 424 426 436 439 439 439 439 439 439 442 447 457 457 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 497 498 498 499 499 500 501 501 502 502 503 504 504 505 505 506 507 508 509 510 511 512 513 514 515 516 517 518 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 536 537 537 538 539 540 541 542 543 544 545 546 547 548 549 550 550 551 552 553 554 555 556 557 558 559 +7 30 31 31 31 31 32 32 32 33 33 39 46 48 53 58 58 58 78 78 182 188 218 228 236 249 304 318 318 318 318 346 358 366 368 368 408 410 414 429 431 447 447 453 453 473 499 508 527 539 550 550 553 560 561 562 563 564 565 565 566 567 568 569 570 571 572 572 573 574 575 576 577 578 578 579 580 581 582 582 583 584 585 586 587 588 588 589 590 590 591 592 593 594 595 596 597 598 599 600 601 602 603 603 604 605 606 607 607 608 609 610 611 612 613 614 614 615 616 617 617 618 619 620 621 621 622 623 624 625 625 626 627 628 629 630 631 632 632 633 634 635 636 637 638 638 639 640 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 654 655 656 657 658 658 659 660 661 661 662 663 664 665 666 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 +8 24 31 31 32 33 33 33 58 78 84 217 217 237 312 312 338 338 338 340 366 366 366 442 453 453 508 519 519 527 539 565 565 566 567 568 568 568 573 575 578 581 583 588 588 590 592 597 597 598 599 603 606 611 612 613 624 626 634 638 638 647 649 652 652 652 660 663 664 684 685 686 687 688 689 690 691 692 693 694 695 696 697 697 698 699 699 700 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 729 730 731 732 733 734 735 736 737 +9 4 4 4 30 31 31 33 64 72 78 84 84 84 207 217 217 217 217 217 217 217 217 217 217 217 217 237 253 257 260 271 271 271 304 318 346 366 417 442 480 503 508 519 539 550 553 553 566 578 581 583 588 590 590 594 606 607 610 612 617 621 625 634 634 638 638 638 652 653 654 657 660 664 664 688 690 691 696 697 702 702 706 706 706 708 708 711 715 723 730 732 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 793 794 795 796 797 798 799 800 801 802 803 804 804 805 806 806 807 808 809 810 811 +10 4 4 4 4 4 5 21 31 31 31 32 32 32 32 53 58 58 72 72 72 72 84 178 178 189 189 202 202 207 207 217 217 217 217 218 249 249 289 320 320 368 370 384 395 407 499 502 502 504 508 522 522 523 527 536 539 548 550 550 553 553 558 558 558 563 574 583 583 588 590 602 602 608 608 624 626 638 644 646 654 664 684 693 704 706 710 711 715 750 754 755 770 770 770 774 780 780 782 782 801 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 887 888 889 890 891 891 892 893 893 894 895 896 897 898 898 899 900 901 902 903 904 +11 4 4 4 32 58 58 58 63 78 114 114 120 120 189 189 189 207 217 223 223 236 243 249 249 318 338 345 345 345 346 349 431 442 502 502 504 504 509 522 527 558 568 568 573 585 588 588 590 590 591 592 594 598 598 598 602 619 619 625 644 652 664 669 678 691 697 697 702 770 770 786 794 812 812 817 817 818 818 819 824 826 832 834 839 843 846 851 853 856 856 859 860 862 865 866 872 880 905 906 907 908 909 910 911 912 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 +12 4 5 24 24 24 31 32 32 32 32 32 33 33 33 46 63 72 78 120 120 120 124 136 136 162 182 189 189 217 217 217 237 237 249 249 249 249 253 271 370 370 370 395 415 416 502 502 502 502 502 514 531 536 540 540 540 540 553 553 558 568 580 585 588 606 625 638 653 664 684 693 693 699 702 714 726 731 755 755 755 755 755 770 786 793 793 794 794 810 810 818 824 832 834 836 842 846 846 851 883 883 883 883 893 898 898 901 916 916 922 923 929 931 932 935 939 941 943 953 954 955 956 957 958 958 958 959 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1013 1014 1015 +13 4 4 4 4 4 4 4 4 24 32 32 35 63 78 83 83 84 84 105 105 114 120 165 188 188 189 204 210 211 223 227 237 247 247 249 249 249 249 249 260 260 289 289 317 325 338 384 405 414 432 439 439 447 463 502 502 502 502 502 502 502 504 509 540 544 544 550 558 568 568 588 593 604 606 606 606 607 617 625 634 653 653 693 712 750 754 755 782 786 794 804 805 810 812 818 836 836 845 846 846 846 847 849 852 868 893 896 915 918 939 943 955 958 965 965 965 974 974 979 986 989 989 997 1000 1002 1016 1017 1018 1019 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 +14 4 4 11 31 31 32 32 32 32 32 33 33 46 46 48 72 72 72 72 72 78 83 95 105 120 120 120 120 120 133 193 210 210 211 211 213 217 217 217 217 218 221 237 249 270 304 320 320 324 325 326 346 349 349 356 405 405 407 424 431 431 439 442 502 502 502 502 527 537 543 544 553 553 558 572 574 579 585 588 588 598 617 625 644 662 674 693 699 742 749 751 755 755 782 836 836 842 842 842 846 851 856 858 867 916 925 943 958 961 1001 1001 1001 1032 1035 1035 1052 1060 1082 1085 1087 1088 1089 1090 1091 1092 1093 1093 1094 1095 1096 1097 1098 1098 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1107 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1140 1141 1142 1143 1144 1145 1146 +15 4 4 7 32 32 32 72 72 72 83 83 84 114 114 120 120 120 120 124 147 162 202 202 218 218 218 228 249 289 298 304 320 320 320 320 324 345 366 366 370 407 412 415 424 439 500 500 502 514 522 546 546 583 585 588 644 644 653 662 668 674 684 702 706 706 782 782 793 793 794 824 836 836 847 867 883 883 901 916 955 959 965 989 1003 1022 1027 1028 1030 1034 1034 1039 1060 1087 1087 1097 1099 1099 1103 1109 1114 1114 1120 1133 1140 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1164 1165 1166 1167 1168 1169 1170 1171 1171 1172 1173 1174 1175 1175 1176 1177 1177 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 +16 4 4 32 33 114 120 370 370 381 439 506 522 527 527 568 588 598 606 606 606 622 653 693 702 706 836 867 901 916 959 964 1019 1025 1097 1097 1177 1177 1177 1192 1193 1193 1202 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 +17 2 2 4 4 4 4 4 4 4 4 4 24 24 31 31 31 31 31 45 45 68 72 72 72 73 84 182 189 189 189 202 211 211 217 217 217 217 217 217 218 218 218 218 218 271 271 271 276 346 370 370 370 370 370 370 403 415 416 527 539 539 539 550 550 550 553 553 553 553 553 553 553 558 558 558 558 558 558 558 568 568 568 606 606 607 674 674 702 738 755 769 782 782 782 787 847 858 858 858 858 867 954 955 965 1087 1087 1087 1087 1087 1087 1087 1087 1087 1087 1087 1098 1149 1219 1223 1226 1233 1235 1236 1236 1236 1237 1238 1239 1240 1241 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 +18 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 22 22 24 24 31 31 33 45 46 46 68 72 72 84 133 133 133 136 179 189 189 189 189 189 189 189 189 202 202 202 207 207 211 211 217 218 218 218 227 268 271 271 289 370 370 370 370 370 370 370 370 370 415 416 416 429 463 504 504 504 510 539 539 550 550 550 553 553 553 553 553 553 553 553 558 558 558 558 558 558 558 558 559 568 568 568 568 588 588 607 607 755 755 755 755 755 782 782 782 908 919 919 954 954 954 954 954 954 954 954 954 954 954 954 955 1087 1087 1087 1087 1087 1087 1087 1087 1087 1149 1149 1155 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1250 1258 1258 1259 1267 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 +19 +20 35 46 58 58 72 73 98 105 117 120 120 133 133 133 136 141 141 156 162 182 202 204 204 209 213 260 270 289 318 337 340 358 372 373 405 496 508 540 553 553 568 583 592 653 684 699 713 738 784 786 845 858 858 867 874 885 893 1029 1034 1061 1098 1098 1098 1111 1133 1155 1187 1187 1214 1219 1228 1283 1304 1305 1306 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1319 1320 1321 1322 1323 1323 1324 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1366 1367 1368 1369 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1388 1389 1390 1391 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 +21 33 45 48 48 56 133 162 165 204 270 325 338 340 340 366 370 370 371 433 434 553 558 572 572 572 583 583 784 806 842 845 847 852 925 979 1083 1177 1181 1187 1219 1256 1256 1290 1307 1331 1333 1339 1342 1342 1345 1352 1358 1367 1369 1378 1379 1381 1392 1392 1410 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1440 1441 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1475 1476 +22 4 4 4 4 4 4 4 4 21 22 32 53 67 73 95 133 133 133 133 179 182 211 217 217 218 218 219 287 310 366 414 496 502 502 502 504 527 527 539 544 550 553 572 572 622 699 706 784 793 836 842 845 845 852 867 867 867 958 965 974 980 1030 1049 1087 1087 1088 1098 1153 1181 1187 1214 1214 1219 1219 1222 1256 1271 1283 1305 1310 1315 1339 1340 1342 1342 1350 1351 1369 1370 1370 1385 1388 1395 1450 1451 1451 1451 1463 1467 1477 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 +23 4 4 4 24 72 72 84 120 120 120 133 136 189 189 210 217 217 217 217 218 236 289 304 320 320 320 320 340 346 347 349 349 508 527 539 550 553 558 563 568 592 618 693 739 782 782 782 816 835 847 867 867 867 867 867 901 923 924 1018 1024 1047 1102 1153 1177 1181 1181 1199 1235 1241 1241 1246 1256 1257 1258 1270 1303 1304 1310 1326 1340 1340 1343 1350 1351 1351 1351 1371 1395 1432 1463 1487 1488 1490 1503 1511 1512 1515 1515 1526 1544 1568 1569 1570 1571 1572 1573 1574 1575 1576 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 +24 4 21 31 32 33 72 72 73 133 189 217 218 227 243 285 320 327 338 340 346 346 366 368 368 370 370 370 370 376 416 417 424 424 430 438 504 504 537 550 550 553 558 568 572 606 606 655 684 786 842 867 867 903 903 954 965 1030 1087 1087 1098 1170 1200 1200 1214 1218 1223 1223 1228 1246 1246 1258 1258 1270 1310 1322 1336 1343 1351 1370 1370 1463 1463 1488 1492 1513 1515 1518 1533 1545 1586 1589 1594 1597 1615 1621 1623 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1643 1644 1645 1646 1646 1646 1647 1648 1649 1650 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 +25 4 4 31 32 72 72 73 73 133 217 217 217 218 271 320 403 539 550 558 702 755 858 858 867 867 903 965 1073 1237 1241 1241 1241 1256 1336 1340 1351 1351 1370 1391 1426 1462 1463 1487 1533 1558 1560 1594 1671 1680 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1707 1708 1709 1710 1711 1712 +26 22 31 32 39 56 58 78 84 105 133 136 141 142 162 182 182 210 219 241 247 249 256 260 318 324 345 358 366 403 416 506 506 509 568 572 572 598 626 642 653 674 674 691 751 793 819 847 868 918 923 923 978 980 987 1039 1082 1119 1155 1182 1199 1203 1206 1303 1345 1368 1368 1388 1388 1392 1398 1406 1415 1441 1477 1521 1521 1557 1575 1630 1646 1651 1704 1713 1714 1715 1716 1717 1718 1719 1719 1720 1721 1722 1723 1724 1725 1726 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1748 1749 1750 1751 1752 1753 1754 1755 1756 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1765 1766 1767 1767 1768 1769 1770 1771 1771 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 +27 32 32 33 35 35 38 58 58 78 78 81 84 89 114 124 124 162 168 182 182 182 202 204 211 211 223 241 249 249 249 259 296 320 320 325 345 346 366 366 366 366 370 395 417 428 433 500 510 527 553 572 598 598 619 626 653 654 675 699 702 702 704 755 805 818 832 842 864 923 955 987 987 987 990 1001 1088 1200 1200 1352 1368 1388 1388 1415 1415 1515 1515 1529 1672 1680 1692 1713 1720 1722 1733 1738 1743 1747 1748 1750 1755 1759 1763 1763 1764 1765 1766 1766 1766 1768 1771 1771 1771 1771 1771 1771 1775 1776 1777 1778 1779 1779 1785 1789 1790 1793 1794 1809 1815 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1893 1894 1895 1896 1897 1898 1899 1900 1901 +28 22 35 58 78 78 133 168 182 204 241 243 249 249 260 324 346 354 366 366 371 372 414 429 447 504 536 538 553 553 558 572 572 594 622 625 637 653 653 654 679 694 699 702 702 704 704 706 706 713 738 818 819 819 832 848 922 923 923 923 925 954 954 965 987 987 1034 1059 1059 1087 1087 1127 1129 1155 1165 1183 1187 1226 1241 1301 1303 1342 1374 1406 1415 1444 1509 1518 1518 1538 1630 1696 1713 1718 1722 1728 1729 1738 1753 1763 1765 1771 1771 1771 1771 1771 1774 1789 1832 1833 1836 1841 1844 1862 1873 1883 1902 1903 1904 1905 1905 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1934 1935 1936 1937 1938 1939 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 +29 4 31 31 31 33 33 58 58 78 133 133 162 179 241 249 260 346 349 370 408 429 527 568 592 621 622 624 625 652 662 699 699 704 753 816 819 819 832 832 868 923 1083 1087 1155 1174 1303 1543 1543 1680 1714 1718 1718 1738 1743 1771 1771 1800 1800 1829 1830 1833 1835 1836 1841 1844 1868 1873 1877 1884 1889 1904 1916 1936 1957 1961 1971 1976 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 +30 31 32 33 33 58 76 83 144 162 204 210 219 221 230 320 340 345 345 347 366 395 403 429 527 540 540 543 548 553 553 583 594 622 652 652 653 662 694 699 709 709 744 753 782 832 858 923 923 923 923 934 954 1003 1026 1083 1083 1088 1111 1174 1290 1303 1303 1360 1521 1630 1651 1725 1738 1763 1771 1779 1787 1788 1799 1838 1852 1856 1871 1873 1874 1883 1902 1902 1903 1915 1916 1930 1931 1941 1961 1975 1997 1998 2014 2014 2015 2015 2015 2029 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 +31 4 7 7 31 31 32 33 33 48 78 78 89 114 133 147 162 182 208 211 241 260 296 315 345 381 395 403 439 494 498 543 553 563 619 623 624 699 704 707 895 923 923 978 978 990 1052 1174 1195 1199 1303 1518 1519 1566 1630 1630 1680 1721 1723 1731 1738 1743 1750 1848 1856 1868 1900 1902 1953 1957 1962 1963 1973 1987 1987 1993 2010 2033 2051 2052 2058 2061 2067 2069 2106 2147 2151 2152 2153 2154 2155 2156 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2209 2210 2211 2212 2213 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 +32 32 32 33 33 48 58 63 78 95 133 253 275 301 354 371 395 405 495 597 662 699 699 707 707 738 778 784 793 805 818 867 899 923 1027 1027 1034 1103 1103 1144 1200 1237 1529 1692 1714 1743 1743 1749 1750 1753 1768 1817 1841 1843 1878 1993 2047 2054 2054 2061 2067 2067 2067 2067 2101 2113 2116 2139 2152 2156 2159 2165 2167 2168 2171 2190 2190 2201 2208 2221 2233 2248 2261 2262 2263 2264 2264 2265 2265 2266 2267 2268 2269 2269 2270 2271 2272 2273 2274 2275 2276 2277 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2299 2300 2301 2302 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2316 2317 2318 2319 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 +33 33 48 58 78 78 114 118 162 167 182 182 193 202 202 204 221 226 249 249 257 261 274 287 287 345 346 368 408 412 457 504 506 576 592 624 653 697 699 699 771 818 832 847 923 923 1003 1103 1175 1199 1200 1202 1223 1370 1519 1696 1723 1723 1750 1771 1777 1848 1903 1929 1935 2014 2030 2053 2061 2072 2151 2152 2152 2153 2168 2168 2221 2243 2247 2262 2263 2264 2288 2291 2293 2293 2303 2323 2333 2335 2340 2352 2370 2371 2372 2373 2374 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2451 2452 2453 2454 2455 2456 2457 2458 2459 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 +34 31 32 32 33 35 35 44 58 58 67 67 70 78 78 83 96 115 136 136 162 182 182 202 202 202 202 217 230 237 249 320 324 340 345 346 366 397 405 414 504 519 540 572 576 593 597 622 625 669 694 694 699 790 815 815 832 835 920 923 923 954 1042 1098 1102 1110 1129 1199 1200 1218 1477 1501 1509 1515 1529 1557 1625 1692 1722 1723 1742 1844 1868 1877 1904 1904 1972 2152 2165 2168 2171 2173 2198 2243 2267 2293 2293 2294 2308 2317 2323 2323 2387 2389 2390 2400 2400 2422 2423 2423 2440 2442 2447 2459 2461 2475 2475 2476 2477 2478 2479 2480 2481 2481 2482 2483 2484 2485 2486 2487 2488 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2527 2528 2529 2530 2531 2532 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 +35 30 32 33 35 58 60 64 76 78 78 84 89 120 133 202 204 204 241 249 249 301 304 340 366 384 416 428 496 508 624 699 700 700 778 815 832 906 923 923 943 987 997 1001 1200 1203 1226 1303 1360 1424 1432 1441 1749 1802 1868 1914 1933 1936 1962 2171 2188 2243 2327 2342 2352 2360 2427 2490 2494 2494 2502 2504 2530 2532 2533 2546 2546 2547 2548 2558 2560 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 +36 32 32 32 58 58 63 89 98 133 133 133 162 162 188 204 213 217 304 345 358 366 366 366 366 381 395 395 395 539 553 623 624 624 640 667 667 667 694 694 699 722 782 815 816 858 922 923 979 1012 1026 1040 1063 1079 1079 1088 1091 1149 1222 1453 1461 1526 1565 1662 1692 1692 1731 1750 1796 1829 1889 1931 1997 2000 2014 2014 2038 2111 2164 2188 2427 2445 2501 2505 2546 2578 2580 2591 2594 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 +37 9 32 32 33 46 58 63 79 84 133 133 162 188 237 247 259 260 273 301 301 304 304 326 340 358 366 424 447 480 504 508 546 580 591 606 617 617 637 640 653 667 667 699 782 793 793 794 895 923 955 1001 1026 1034 1034 1061 1087 1088 1088 1127 1147 1148 1155 1166 1177 1177 1228 1249 1303 1323 1339 1343 1368 1388 1441 1441 1444 1515 1630 1645 1800 1809 1972 2000 2054 2117 2151 2157 2164 2292 2360 2580 2591 2591 2617 2624 2625 2626 2631 2632 2632 2633 2634 2635 2638 2642 2643 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 +38 4 32 32 33 46 58 63 78 88 179 182 204 204 211 241 249 249 259 301 345 346 346 366 412 415 416 416 438 439 447 447 447 504 527 527 539 540 553 553 558 661 667 667 667 674 699 722 725 725 782 794 797 812 842 842 842 923 954 954 1129 1137 1144 1181 1202 1259 1339 1339 1444 1543 1617 1680 1705 1713 1874 1874 1878 2030 2054 2153 2224 2256 2307 2360 2413 2461 2481 2546 2580 2617 2619 2633 2635 2660 2661 2662 2663 2664 2665 2666 2669 2679 2680 2681 2689 2694 2695 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 +39 4 4 32 32 55 58 58 70 95 124 133 141 177 186 211 211 217 247 249 249 259 260 301 304 312 312 326 329 338 340 345 366 366 408 447 447 502 509 509 510 522 527 553 558 566 598 612 619 621 622 625 625 625 640 653 653 653 667 667 667 674 699 715 741 753 868 954 955 958 1027 1034 1063 1102 1120 1147 1193 1241 1241 1303 1483 1672 1719 1803 1868 1868 1973 2003 2014 2138 2138 2211 2248 2450 2494 2580 2615 2617 2617 2624 2625 2625 2625 2633 2635 2661 2662 2666 2669 2679 2682 2689 2711 2713 2725 2725 2728 2729 2732 2734 2737 2740 2746 2748 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2780 2781 2782 2783 2784 2785 +40 35 44 120 131 202 207 304 304 336 381 434 537 553 566 572 867 1039 1170 1222 1235 1259 1270 1357 1501 1575 1575 1617 1749 1795 1868 1889 2434 2627 2716 2756 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2799 2800 2801 2802 2803 2804 2804 2805 2806 2807 2808 2809 2810 2811 2812 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 +41 8 21 26 28 28 31 32 33 33 33 38 42 46 46 48 49 49 53 55 57 58 58 59 60 61 61 63 65 66 67 68 72 73 73 78 82 84 84 84 85 85 88 92 94 96 96 103 103 111 113 115 122 124 133 138 142 143 148 151 153 154 160 162 165 166 167 167 172 174 178 178 179 180 182 183 186 188 189 189 189 189 196 199 201 202 202 202 202 202 204 205 213 218 218 239 255 256 268 268 271 277 277 278 289 289 290 290 291 299 340 366 368 371 371 376 407 414 415 553 598 623 625 816 816 835 965 965 965 1082 1086 1129 1129 1172 1222 1241 1574 1575 1630 1673 1707 1825 1884 1928 2158 2293 2494 2619 2722 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2853 2854 2855 2856 2857 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 +42 4 5 24 28 32 32 33 44 44 48 49 51 52 55 56 56 56 59 65 70 72 72 73 78 96 97 103 111 111 113 117 123 124 135 136 136 136 141 143 148 152 153 167 178 179 182 185 186 188 190 217 218 226 230 232 235 236 241 244 246 247 249 255 259 260 260 260 261 268 268 268 273 274 276 277 278 279 282 287 289 290 291 291 293 294 295 304 305 308 310 326 340 340 349 368 376 381 384 403 406 407 407 414 417 450 454 510 535 540 553 554 702 710 738 789 889 964 1087 1087 1155 1200 1241 1241 1388 1432 1681 1696 1822 1962 1974 2014 2371 2372 2400 2409 2450 2619 2624 2781 2877 2878 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 +43 5 24 31 32 32 32 35 46 47 49 56 67 68 68 72 73 73 120 123 124 130 135 141 143 165 167 177 179 189 189 190 213 213 217 220 223 228 232 241 246 255 256 268 271 276 282 284 287 287 290 305 306 306 324 325 326 326 340 347 358 362 368 371 372 376 376 377 381 388 389 390 394 395 397 398 399 403 404 405 407 411 415 415 415 442 447 450 539 550 553 593 691 699 741 816 856 868 954 1097 1097 1102 1191 1195 1214 1214 1259 1351 1368 1390 1417 1477 1545 1545 1680 1841 1868 1868 1962 2223 2248 2293 2360 2475 2475 2506 2508 2510 2514 2625 2673 2678 2779 2877 2888 2915 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 +44 4 21 24 31 41 46 48 49 72 73 83 103 105 120 120 120 124 124 130 136 136 136 165 179 189 189 193 202 217 218 218 219 222 227 227 227 228 232 243 249 268 271 275 289 320 324 325 327 329 334 349 354 356 363 376 388 395 404 405 407 421 431 431 439 439 442 447 450 454 457 473 489 504 504 504 504 504 510 550 550 553 553 568 593 709 753 883 959 964 964 965 987 1047 1052 1086 1087 1097 1097 1241 1259 1432 1501 1696 1829 1865 2240 2469 2484 2726 2727 2847 2914 2933 2946 2947 2947 2948 2974 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2989 2990 2991 2992 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 +45 4 21 22 105 114 114 120 120 124 124 130 156 162 188 189 202 217 217 217 232 241 249 289 289 325 340 408 414 415 419 423 428 429 429 432 437 439 462 474 474 478 480 485 496 497 497 498 500 502 504 508 509 509 509 527 527 528 530 532 532 533 533 535 539 543 543 547 554 554 559 864 868 912 930 930 954 965 971 1002 1019 1087 1129 1149 1163 1183 1200 1214 1214 1237 1241 1259 1259 1318 1350 1390 1424 1501 1526 1538 1678 1714 1829 1829 1886 1973 2014 2086 2198 2198 2243 2371 2421 2678 2753 2758 2766 2851 2877 2995 2995 3003 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3020 3021 3022 3023 3024 3024 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3034 3035 3036 3037 3038 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 +46 +47 21 31 32 33 33 58 58 64 78 204 207 217 217 260 260 318 366 366 408 504 508 508 539 550 553 567 568 568 568 568 568 570 573 574 576 577 580 588 590 590 591 594 597 597 598 599 601 603 604 606 608 608 609 610 612 613 614 623 624 625 626 634 635 638 641 642 643 646 646 649 652 652 653 659 662 663 664 672 678 690 693 699 703 708 712 713 715 717 725 726 727 733 751 763 782 790 792 819 823 841 849 867 868 887 919 934 939 939 939 954 1052 1087 1130 1270 1276 1303 1451 1673 1679 2400 2591 2591 2627 2706 2856 2926 3042 3075 3075 3082 3083 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 +48 21 31 31 58 67 181 181 217 217 217 217 340 346 346 356 366 366 499 527 527 527 539 553 568 588 588 588 590 590 606 606 612 623 626 630 637 638 638 638 641 650 652 652 653 664 687 691 700 706 711 711 714 715 716 721 723 725 729 738 741 745 753 762 763 764 765 765 767 769 774 780 782 783 785 786 787 787 791 796 802 803 810 810 811 838 849 858 894 895 942 951 954 954 979 1019 1027 1039 1044 1057 1170 1174 1203 1222 1222 1572 1691 1829 1953 1953 1987 1987 2014 2014 2135 2241 2736 2793 2912 2962 3100 3121 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 +49 4 4 4 4 32 32 33 33 58 58 72 72 83 84 105 157 162 162 162 162 179 179 179 213 217 217 217 217 217 243 318 320 321 336 346 366 368 384 388 416 424 504 539 550 550 550 553 553 553 553 558 570 573 583 588 590 635 638 644 654 664 691 691 697 702 704 711 750 753 753 754 755 755 756 757 759 760 762 763 765 770 774 775 799 807 817 818 819 823 837 838 842 842 844 845 846 846 849 851 854 858 858 858 858 860 865 866 868 874 875 878 880 880 885 885 887 889 889 891 891 892 895 897 922 925 942 951 954 970 1019 1030 1034 1047 1049 1060 1097 1170 1187 1195 1199 1214 1241 1241 1241 1241 1241 1259 1405 1465 1501 1529 1575 1622 1679 1768 1889 2276 2494 2576 2627 2678 2678 2847 2973 3006 3043 3063 3073 3147 3155 3165 3166 3167 3168 3169 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 +50 31 32 32 58 63 114 219 247 320 345 345 358 366 366 366 395 416 424 502 504 504 509 539 544 550 550 554 558 558 558 569 580 580 583 583 588 590 598 604 612 617 617 624 636 638 664 693 693 710 715 750 770 770 770 780 786 790 813 817 818 818 819 824 825 826 832 834 836 836 839 839 843 846 846 851 853 853 854 856 858 858 859 860 867 868 869 872 874 875 875 878 880 885 885 887 887 889 902 909 915 920 923 924 925 928 931 932 933 939 939 940 941 943 951 951 954 954 1002 1007 1083 1107 1111 1235 1235 1241 1303 1679 1742 1841 1841 1862 1872 2135 2165 2276 2276 2390 2627 2729 2745 2748 3105 3117 3182 3184 3194 3197 3197 3204 3208 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 +51 22 32 32 32 32 32 33 33 46 58 58 63 72 114 182 217 217 217 218 247 247 260 260 345 358 366 424 439 539 539 553 553 558 558 591 604 608 623 623 693 698 702 731 738 750 770 770 782 786 794 815 817 818 819 824 825 832 834 843 846 856 862 862 862 868 880 895 896 902 909 913 916 919 922 924 926 927 928 929 931 932 933 939 939 941 943 943 944 946 948 948 954 954 954 954 955 957 959 968 970 971 973 974 975 978 983 986 987 1000 1004 1007 1010 1014 1034 1061 1077 1079 1083 1087 1121 1149 1149 1153 1153 1187 1259 1707 1742 1742 1750 1829 1841 1841 1946 2047 2168 2188 2276 2306 2683 3004 3047 3075 3077 3104 3171 3194 3225 3235 3237 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 +52 4 22 31 32 33 58 72 72 105 124 204 218 218 218 218 218 227 227 247 249 287 326 439 480 489 502 502 502 502 502 502 504 504 539 540 553 553 558 568 572 574 583 585 588 606 606 606 606 684 693 699 709 751 755 765 793 804 829 836 846 846 849 851 868 881 898 911 915 916 916 939 943 949 954 954 955 956 957 958 961 962 965 969 972 974 974 978 978 987 989 996 1000 1001 1002 1003 1005 1016 1017 1021 1023 1029 1030 1030 1034 1034 1034 1035 1043 1050 1052 1055 1055 1058 1060 1060 1063 1064 1068 1071 1083 1106 1109 1111 1114 1127 1127 1152 1170 1174 1183 1204 1235 1259 1471 1506 1545 1660 1679 1696 1841 1841 1869 1874 1874 1974 2014 2014 2151 2152 2168 2188 2243 2307 2532 2532 2678 2736 2758 2764 3065 3152 3171 3192 3255 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 +53 32 33 48 69 105 120 120 188 188 189 189 204 204 204 217 217 218 247 247 249 249 260 271 320 320 320 349 370 405 408 408 416 438 500 500 502 502 502 502 502 509 543 544 546 552 553 553 566 588 588 607 617 652 654 654 662 693 699 812 819 842 842 847 851 858 868 868 868 916 955 959 964 965 983 1001 1003 1009 1019 1021 1022 1023 1024 1030 1033 1034 1041 1047 1057 1060 1061 1063 1064 1064 1076 1083 1085 1097 1097 1099 1107 1109 1109 1111 1114 1120 1120 1120 1121 1129 1129 1129 1129 1129 1129 1129 1132 1138 1145 1153 1155 1174 1182 1187 1199 1200 1214 1228 1258 1259 1270 1276 1386 1451 1506 1528 1553 1622 1696 1718 1809 2048 2138 2138 2248 2326 2532 2619 2758 2758 3034 3047 3071 3079 3085 3096 3096 3264 3275 3277 3278 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 +54 32 48 78 105 114 120 120 120 120 182 204 204 211 241 249 303 303 304 324 324 346 349 366 366 366 366 405 502 502 502 502 502 522 544 544 546 585 588 588 588 597 617 644 653 699 725 782 836 836 868 875 959 961 980 1001 1001 1027 1034 1052 1059 1083 1087 1087 1093 1099 1102 1102 1103 1109 1109 1110 1111 1114 1119 1120 1120 1127 1129 1130 1131 1133 1138 1140 1141 1141 1144 1153 1165 1169 1189 1192 1214 1241 1258 1258 1270 1392 1505 1509 1526 1526 1575 1678 1684 1684 1915 2014 2138 2269 2678 2761 2761 2874 2997 3003 3049 3054 3075 3138 3138 3316 3319 3325 3330 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 +55 4 4 4 30 31 31 32 32 32 32 33 202 228 247 249 276 320 320 338 345 346 366 366 366 366 366 366 366 366 366 381 439 442 462 509 546 568 588 588 653 653 653 653 653 674 674 751 782 842 842 842 847 847 847 989 1025 1027 1030 1030 1034 1041 1060 1097 1097 1097 1098 1114 1140 1148 1150 1151 1155 1168 1169 1169 1170 1170 1177 1183 1183 1187 1189 1192 1193 1196 1200 1205 1206 1210 1213 1226 1227 1229 1233 1234 1234 1241 1256 1258 1258 1258 1270 1270 1303 1392 1415 1451 1501 1550 1662 1673 1679 1680 1742 1794 1822 2014 2014 2461 2493 2713 2764 3079 3098 3121 3147 3152 3194 3254 3316 3360 3361 3365 3372 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 +56 4 4 4 32 114 120 217 218 218 358 366 366 370 370 381 426 506 506 540 543 553 558 558 568 588 588 638 810 810 867 939 964 1019 1060 1087 1087 1098 1153 1177 1214 1218 1219 1234 1241 1241 1242 1258 1258 1270 1276 1286 1371 1424 1451 1501 1678 1679 1679 1679 1745 2242 2706 2791 2941 3194 3284 3360 3392 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 +57 4 4 4 24 24 24 25 31 31 45 46 68 72 72 72 72 72 72 73 73 133 182 189 189 189 189 202 217 217 217 217 218 218 218 218 218 218 218 218 218 271 345 345 345 345 346 346 346 366 366 370 370 370 370 370 407 434 489 527 539 539 539 539 550 550 550 550 550 550 553 553 553 553 558 558 568 568 588 607 618 653 716 782 816 867 867 867 954 954 954 954 1041 1087 1087 1087 1087 1177 1187 1218 1223 1223 1235 1236 1236 1236 1238 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1246 1253 1254 1258 1258 1259 1265 1265 1266 1266 1270 1273 1278 1286 1290 1303 1451 1526 1550 1588 1679 1679 1679 1680 1692 1822 2014 2047 2242 2369 2501 2823 2941 3075 3075 3075 3079 3079 3079 3372 3372 3372 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 +58 4 4 4 4 4 4 4 4 4 4 4 21 24 24 24 24 24 24 31 31 31 32 32 33 46 46 72 72 72 84 133 133 189 189 189 189 189 202 202 202 213 217 217 217 217 217 218 218 234 271 312 370 370 370 370 370 370 434 527 527 527 539 539 550 553 553 568 568 607 755 755 782 858 908 919 954 954 954 954 954 1087 1087 1087 1087 1087 1087 1087 1087 1087 1087 1149 1223 1238 1238 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1242 1258 1258 1258 1258 1259 1259 1281 1283 1286 1287 1290 1424 1673 1679 1679 1679 1692 1704 2014 2591 2753 2941 2957 2957 2957 2957 3079 3194 3194 3194 3194 3194 3194 3416 3429 3438 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 +59 4 4 24 31 32 33 46 46 72 120 120 133 179 189 189 189 189 189 189 202 217 218 218 218 268 370 370 539 550 550 553 553 558 558 558 558 558 568 568 568 755 755 782 782 843 858 954 954 955 965 965 965 1039 1087 1087 1087 1214 1235 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1241 1283 1286 1538 1538 1675 1679 1679 1679 1679 2014 2238 2761 2941 2957 3075 3079 3194 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3485 3486 +60 4 4 4 24 24 31 31 32 33 56 72 105 136 189 189 207 209 209 209 217 217 217 217 217 218 247 260 271 301 306 312 317 320 354 408 548 550 553 553 558 568 568 568 568 568 568 568 613 619 619 623 635 637 652 653 684 706 738 784 847 853 858 894 939 954 965 985 1087 1151 1187 1193 1223 1226 1237 1334 1338 1339 1342 1343 1355 1375 1375 1378 1411 1422 1428 1441 1460 1468 1471 1515 1617 1673 1809 1829 1841 1905 2326 2494 2510 2532 2706 2707 2729 2745 3075 3194 3264 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 +61 4 4 33 33 45 48 58 73 83 105 120 120 120 133 133 202 204 204 217 218 285 289 312 320 320 320 324 327 340 370 432 438 485 502 504 508 509 509 527 540 544 550 558 563 699 739 770 845 867 867 867 922 965 1022 1030 1047 1082 1083 1086 1097 1098 1109 1177 1206 1214 1241 1270 1271 1303 1305 1307 1310 1312 1315 1337 1339 1339 1340 1342 1345 1353 1365 1367 1367 1369 1370 1378 1379 1379 1379 1381 1381 1382 1383 1388 1388 1392 1392 1415 1424 1443 1450 1451 1451 1457 1458 1459 1460 1467 1475 1515 1515 1523 1526 1544 1560 1621 1623 1679 1681 1696 1705 1779 1796 1805 1972 2014 2018 2138 2240 2341 2400 2706 2736 2753 2753 2765 2877 2903 2957 2957 2957 3079 3171 3252 3293 3311 3325 3455 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 +62 4 21 21 24 45 53 72 72 73 120 133 133 146 156 179 204 204 217 312 320 320 338 340 349 366 407 429 502 502 502 502 502 553 553 558 637 699 699 725 739 784 784 816 845 852 867 867 884 949 965 979 1087 1097 1097 1097 1098 1098 1109 1182 1187 1218 1246 1256 1310 1315 1339 1340 1342 1342 1342 1345 1350 1351 1351 1351 1365 1371 1379 1381 1388 1395 1407 1450 1450 1451 1451 1451 1463 1467 1482 1483 1486 1487 1489 1490 1493 1494 1497 1498 1500 1501 1502 1504 1504 1505 1507 1508 1508 1511 1513 1514 1515 1518 1519 1520 1522 1523 1523 1525 1526 1526 1527 1529 1530 1531 1532 1538 1538 1540 1541 1543 1552 1553 1553 1557 1560 1561 1562 1563 1629 1630 1678 1679 1680 1690 1796 1839 1962 1965 2134 2161 2400 2483 2867 2925 3047 3194 3248 3360 3583 3627 3628 3629 3630 3631 3632 3633 3634 3635 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 +63 4 4 21 22 72 73 120 120 133 133 133 136 165 232 320 320 327 340 439 508 527 537 539 539 550 696 702 702 782 784 784 835 841 847 867 1072 1098 1098 1098 1098 1102 1170 1177 1181 1214 1214 1219 1241 1241 1246 1256 1256 1271 1310 1339 1342 1342 1395 1451 1451 1467 1501 1512 1515 1515 1515 1517 1526 1544 1550 1560 1568 1572 1575 1576 1580 1581 1589 1591 1593 1598 1605 1609 1616 1617 1618 1619 1621 1623 1630 1704 1711 1712 1723 1780 1868 2014 2135 2619 2812 2847 2979 3047 3047 3075 3075 3139 3251 3426 3537 3587 3591 3612 3612 3651 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 +64 4 4 4 4 19 32 33 35 48 118 120 133 133 217 217 227 289 340 368 441 443 550 558 782 813 842 842 842 852 867 867 867 901 954 1241 1310 1336 1340 1350 1351 1351 1370 1370 1371 1501 1513 1538 1587 1589 1594 1597 1615 1615 1621 1632 1636 1637 1637 1638 1640 1642 1646 1646 1647 1650 1651 1652 1656 1661 1662 1663 1663 1671 1672 1681 1684 1689 1694 1702 1707 2014 2753 2812 3121 3499 3687 3694 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 +65 4 4 4 4 4 4 4 22 31 31 32 32 72 73 84 162 217 318 321 346 370 370 508 550 558 755 784 842 954 965 1087 1226 1241 1276 1336 1351 1557 1594 1623 1671 1680 1689 1697 1702 1703 2014 2014 2014 2117 2135 2792 2941 2941 3056 3152 3177 3326 3703 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 +66 32 39 58 204 275 318 318 320 371 407 408 408 414 417 432 473 626 649 653 653 653 653 655 667 668 668 674 674 793 797 939 1030 1113 1115 1203 1210 1319 1352 1388 1529 1557 1566 1714 1716 1720 1722 1725 1726 1729 1738 1742 1742 1745 1745 1747 1748 1748 1750 1751 1755 1756 1758 1759 1763 1764 1766 1766 1766 1768 1771 1771 1777 1779 1779 1781 1784 1785 1785 1791 1794 1796 1798 1799 1800 1802 1803 1807 1808 1809 1810 1811 1812 1813 1815 1820 1821 1836 1841 1842 1864 1868 1870 1871 1875 1889 1895 1901 1902 1929 1936 1961 1980 2014 2017 2018 2060 2106 2186 2202 2237 2301 2360 2360 2389 2450 2491 2494 2494 2521 2546 2591 2617 2745 2745 2748 2748 2781 3038 3075 3293 3311 3378 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3770 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 +67 14 32 32 32 48 58 84 133 133 168 183 211 259 260 366 395 395 408 433 439 547 558 563 566 572 572 580 583 591 598 622 625 642 653 654 662 662 684 694 699 704 731 755 797 816 818 818 819 832 832 858 910 922 923 925 954 987 1034 1200 1352 1415 1477 1501 1521 1543 1543 1630 1692 1726 1738 1747 1748 1750 1763 1763 1765 1766 1771 1771 1771 1771 1776 1777 1785 1809 1819 1822 1824 1826 1832 1837 1840 1841 1841 1843 1844 1845 1852 1862 1863 1868 1871 1879 1882 1882 1885 1886 1888 1889 1891 1892 1896 1897 1899 1901 1903 1908 1909 1936 1953 1961 1963 1968 1972 1973 2000 2000 2030 2053 2099 2107 2138 2151 2242 2243 2361 2372 2400 2442 2470 2494 2501 2506 2563 2729 2741 2939 3047 3075 3079 3181 3219 3311 3369 3707 3707 3758 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 +68 33 58 72 78 98 133 133 168 193 204 217 217 217 241 241 249 354 366 366 366 366 414 439 447 463 494 537 592 607 619 625 632 635 637 653 654 668 675 675 679 699 794 819 842 923 925 958 1048 1079 1174 1374 1386 1388 1521 1543 1575 1753 1761 1771 1771 1829 1832 1856 1856 1862 1868 1868 1874 1874 1877 1899 1914 1916 1928 1928 1928 1929 1930 1931 1933 1933 1933 1937 1941 1943 1945 1946 1953 1953 1958 1959 1962 1963 1965 1965 1965 1968 1969 1971 1973 1980 2005 2014 2014 2016 2018 2028 2039 2042 2106 2111 2136 2147 2151 2151 2185 2237 2292 2360 2400 2440 2459 2470 2494 2578 2587 2653 2741 2881 2918 3075 3079 3079 3096 3464 3748 3789 3795 3800 3800 3801 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3867 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 +69 32 33 33 33 58 63 188 228 239 247 249 260 296 325 340 345 366 371 395 432 540 550 568 624 652 662 684 694 699 736 782 816 863 868 922 923 923 923 1027 1034 1061 1165 1170 1199 1241 1301 1303 1395 1680 1717 1721 1768 1810 1824 1833 1836 1856 1874 1884 1903 1916 1930 1957 2014 2030 2040 2048 2051 2052 2053 2058 2060 2061 2065 2084 2091 2094 2102 2103 2117 2117 2122 2128 2137 2141 2147 2152 2218 2243 2288 2361 2440 2481 2494 2546 2546 2572 2600 2614 2625 2655 2748 2751 2764 2874 2957 2957 3075 3075 3075 3312 3764 3784 3833 3835 3844 3854 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 +70 32 32 38 48 58 58 78 112 204 210 211 226 226 231 236 287 366 366 371 550 644 653 694 699 744 782 784 845 923 955 1009 1024 1027 1034 1036 1042 1052 1083 1220 1226 1303 1380 1441 1518 1538 1543 1630 1630 1738 1753 1777 1788 1836 1841 1856 1871 1916 1936 1936 1957 2000 2014 2015 2027 2033 2033 2046 2047 2052 2058 2061 2063 2067 2074 2083 2083 2099 2106 2113 2116 2122 2125 2132 2138 2145 2147 2152 2153 2156 2157 2161 2162 2165 2168 2170 2183 2186 2187 2188 2191 2193 2204 2205 2206 2207 2209 2224 2228 2230 2253 2258 2301 2302 2440 2440 2442 2459 2482 2494 2613 2624 2625 2668 2736 2851 2979 3004 3006 3059 3150 3281 3464 3464 3667 3801 3854 3862 3863 3880 3890 3891 3922 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 +71 33 63 105 143 162 217 260 321 371 372 390 408 429 432 540 572 613 621 642 694 699 707 784 789 793 842 842 895 918 923 923 923 1027 1034 1072 1157 1160 1199 1303 1357 1432 1543 1731 1743 1746 1750 1836 1836 1852 1870 1872 1875 1987 1995 2010 2061 2067 2125 2141 2147 2147 2151 2152 2156 2158 2186 2203 2207 2208 2224 2233 2258 2261 2263 2266 2268 2269 2272 2276 2280 2293 2301 2306 2323 2329 2336 2344 2352 2361 2440 2442 2459 2459 2494 2542 2576 2591 2726 2728 2979 2979 3047 3205 3205 3292 3321 3507 3587 3743 3790 3863 3870 3979 4006 4007 4008 4009 4010 4011 4012 4013 4014 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 +72 4 5 24 31 33 48 58 88 98 162 217 231 268 366 372 429 486 527 566 592 694 738 923 985 1019 1019 1200 1352 1432 1630 1675 1690 1692 1726 1753 1870 1871 1878 1946 1954 2082 2158 2202 2221 2234 2263 2276 2288 2293 2301 2303 2333 2371 2372 2373 2386 2390 2400 2413 2417 2423 2426 2433 2437 2440 2442 2443 2447 2450 2455 2458 2460 2462 2465 2470 2471 2494 2494 2494 2527 2558 2560 2568 2625 2741 2799 2851 2898 2914 2984 3063 3100 3150 3201 3309 3311 3318 3348 3348 3356 3356 3612 3784 3801 3844 3906 3913 3918 3979 4011 4052 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 +73 32 33 33 33 35 35 44 58 58 78 104 217 237 257 320 351 397 405 408 416 502 508 528 583 625 686 738 753 918 920 987 987 1009 1019 1052 1144 1175 1175 1199 1223 1377 1483 1515 1615 1622 1722 1726 1742 1877 1896 1915 1915 1946 1974 2061 2171 2221 2301 2323 2325 2327 2340 2385 2386 2387 2420 2423 2442 2445 2459 2494 2521 2528 2528 2537 2544 2558 2560 2561 2561 2563 2591 2594 2666 2805 2928 2972 3004 3042 3147 3177 3251 3311 3588 3641 3741 3742 3784 3785 3851 3898 3908 4109 4172 4179 4187 4188 4189 4190 4190 4191 4192 4193 4194 4195 4196 4196 4197 4198 4199 4200 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 +74 32 32 32 58 58 124 193 202 204 204 213 217 260 340 366 366 395 415 485 527 553 589 591 625 625 642 668 684 694 699 812 818 923 1003 1034 1034 1042 1199 1200 1200 1203 1471 1526 1675 1868 1884 1904 1914 1931 1950 2062 2111 2165 2165 2168 2243 2292 2292 2356 2360 2387 2389 2400 2423 2427 2440 2445 2491 2491 2494 2527 2532 2533 2542 2542 2546 2547 2555 2558 2558 2560 2563 2567 2583 2585 2591 2592 2593 2598 2600 2603 2609 2610 2617 2636 2638 2639 2666 2691 2736 2741 2755 2851 2874 3228 3264 3348 3356 3503 3725 3725 3835 3912 4036 4048 4115 4149 4210 4232 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4319 4320 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 +75 7 32 32 33 72 76 98 210 217 228 301 301 304 320 346 366 366 395 432 508 566 572 572 617 624 640 699 770 815 860 906 916 923 923 1061 1063 1219 1222 1229 1253 1263 1270 1303 1421 1692 1742 1822 1841 1868 1868 1870 1874 1941 2015 2117 2138 2138 2165 2214 2237 2241 2243 2292 2459 2475 2494 2494 2505 2546 2547 2560 2589 2598 2604 2617 2622 2625 2626 2627 2631 2638 2645 2652 2655 2680 2686 2696 2730 2801 3075 3417 3425 3503 3707 3750 3826 4291 4320 4321 4335 4335 4336 4337 4338 4339 4340 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 +76 32 33 33 33 60 63 68 89 133 156 188 237 260 301 304 368 395 432 617 617 619 624 640 652 667 694 699 713 772 815 860 923 1026 1069 1079 1222 1398 1444 1505 1681 1787 1800 1822 1953 2000 2014 2151 2164 2188 2243 2243 2292 2400 2494 2494 2580 2591 2592 2617 2623 2626 2628 2632 2638 2645 2654 2664 2669 2696 2701 2735 2746 2801 2851 2997 3075 3079 3588 3676 3682 4036 4048 4189 4343 4344 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 +77 8 32 32 32 33 33 33 58 58 105 120 124 133 133 156 162 228 287 301 304 349 366 366 395 414 442 447 447 527 553 568 625 640 640 653 653 667 667 699 702 738 797 835 847 954 963 1034 1069 1083 1121 1214 1271 1339 1543 1679 1720 1742 1829 1844 2111 2153 2188 2241 2248 2248 2413 2413 2427 2494 2497 2560 2576 2580 2613 2633 2635 2661 2662 2663 2669 2676 2680 2684 2687 2689 2695 2713 2721 2723 2726 2730 2732 2736 2740 2741 2743 2744 2745 2746 2747 2748 2748 2749 2751 2753 2775 2855 2874 2947 2957 3082 3205 3284 3294 3952 4115 4115 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 +78 32 46 58 60 60 78 124 133 144 162 182 182 186 210 211 211 211 287 304 312 324 345 358 366 381 414 415 416 447 447 487 504 527 565 621 622 623 625 652 653 667 699 725 739 782 858 898 965 974 1027 1034 1052 1058 1061 1087 1169 1196 1241 1451 1477 1633 1719 1787 2138 2224 2243 2269 2360 2413 2580 2616 2624 2625 2635 2635 2665 2679 2682 2713 2725 2729 2729 2732 2734 2736 2740 2741 2743 2748 2753 2757 2760 2766 2767 2768 2770 2775 2776 2777 2780 2780 2780 2781 2782 2783 2784 2785 2925 2957 3007 3053 3075 3075 3082 3127 3152 3299 3464 3510 3510 3536 3536 3746 4115 4182 4387 4402 4410 4411 4412 4413 4414 4415 4416 4417 4417 4418 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 +79 4 4 31 32 33 46 48 55 58 58 59 114 120 133 144 162 182 182 217 228 235 247 249 249 287 304 345 366 366 370 381 395 408 424 426 439 442 502 504 558 568 625 637 653 653 667 668 755 755 784 810 858 858 901 954 954 954 964 974 989 1027 1049 1063 1087 1087 1120 1214 1241 1241 1303 1406 1451 1462 1501 1501 1501 1678 1678 1718 1809 1880 1888 1889 1901 1972 2014 2070 2293 2360 2372 2494 2501 2578 2591 2615 2615 2617 2617 2617 2633 2661 2665 2678 2679 2691 2711 2713 2734 2757 2758 2759 2759 2760 2762 2767 2768 2770 2771 2775 2780 2784 2941 3018 3063 3071 3075 3075 3079 3313 3426 3464 3676 3731 3835 4287 4383 4396 4402 4420 4423 4428 4434 4434 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 +80 32 33 58 72 79 179 202 207 217 304 304 338 366 381 395 498 553 553 553 576 591 623 674 694 782 810 845 857 918 1087 1120 1120 1157 1229 1241 1241 1339 1511 1660 1704 1829 2117 2269 2533 2617 2661 2665 2665 2711 2759 2767 2768 2780 2784 2816 2847 2941 3005 3251 3464 4290 4387 4390 4390 4395 4418 4422 4427 4427 4457 4462 4462 4463 4464 4466 4470 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 +81 3 4 4 5 7 9 11 12 19 23 29 32 33 35 35 36 39 43 45 48 48 49 51 52 55 56 57 58 58 59 60 61 63 64 65 67 68 70 72 72 72 72 72 73 73 76 77 80 82 83 84 84 84 84 85 85 88 92 94 96 97 98 98 99 101 103 133 141 141 153 156 157 157 157 165 167 169 178 179 183 186 188 189 189 198 199 204 205 217 217 217 218 218 227 239 249 256 277 278 289 290 301 354 358 366 368 370 370 371 371 376 395 407 415 424 430 536 553 614 623 625 636 784 965 1030 1195 1241 1465 1526 1575 1617 1673 1673 1679 1953 2030 2223 2480 2812 2836 2841 2853 2853 2856 2879 2918 2957 3006 3146 3205 3326 3676 3676 3676 3707 4391 4489 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4561 4562 +82 5 7 12 21 24 29 32 44 49 49 52 52 55 56 58 58 58 60 65 68 73 73 97 99 103 113 115 126 126 131 136 136 136 136 141 142 143 147 147 148 150 151 154 158 165 165 167 179 179 182 182 186 188 189 192 197 199 231 232 235 240 249 255 260 268 268 268 270 271 274 275 276 278 280 287 289 289 290 291 295 301 304 305 307 320 320 320 320 370 372 403 404 407 415 454 502 784 786 842 959 964 964 964 965 965 1030 1088 1200 1219 1223 1352 1390 1501 1868 2113 2159 2409 2447 2480 2483 2552 2624 2624 2690 2845 2846 2848 2869 2878 2903 2907 2914 2917 2918 2924 2948 2957 2957 2957 2968 2973 2990 3030 3045 3047 3194 3197 3667 3835 3890 3921 4115 4429 4489 4524 4529 4529 4535 4538 4541 4544 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 +83 5 19 23 28 31 32 32 42 48 49 52 73 73 96 97 103 105 135 141 142 143 162 167 179 207 218 227 232 235 244 246 250 255 257 261 262 268 273 274 275 276 277 280 282 283 284 289 290 292 295 297 304 331 340 354 358 371 371 376 388 390 395 395 398 403 404 407 407 414 417 450 553 699 702 816 816 829 868 955 1102 1102 1241 1323 1388 1528 1874 1874 2243 2430 2619 2756 2758 2848 2848 2856 2893 2898 2901 2903 2911 2914 2915 2917 2918 2941 2946 2962 2965 2997 3002 3030 3045 3282 3284 3356 3968 4280 4345 4529 4546 4582 4582 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 +84 4 4 4 21 22 22 24 71 73 103 103 124 124 130 136 143 162 165 178 202 217 218 232 243 246 247 249 255 271 271 276 276 286 289 290 296 311 315 320 328 328 335 337 338 340 340 342 354 358 381 381 389 390 395 399 399 403 404 407 409 414 417 434 439 442 451 453 454 457 504 553 568 607 653 842 959 1097 1155 1219 1223 1241 1342 1351 1395 1406 1675 1681 1742 1792 2215 2341 2469 2806 2848 2903 2903 2914 2931 2934 2965 2991 2991 3045 3047 3186 3194 3290 3890 3928 4411 4569 4601 4605 4615 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 diff --git a/Matlab/README.txt b/Matlab/README.txt new file mode 100644 index 0000000000..04258764b2 --- /dev/null +++ b/Matlab/README.txt @@ -0,0 +1,4 @@ +This directory contains some basic concepts on Bayes filtering. +Main scripts : + RecursivesBayes.m + RecursivesBayesAvpd.m \ No newline at end of file diff --git a/Matlab/RecursiveBayes.m b/Matlab/RecursiveBayes.m new file mode 100644 index 0000000000..9dd7300e65 --- /dev/null +++ b/Matlab/RecursiveBayes.m @@ -0,0 +1,187 @@ +%%%%%%%%%%%%%%%%%% +%Example of the recursive Bayes filtering +%In this example, the memory is fixed (each new signatures aren't added to +%the memory after each iteration). The likelihood is also predefined to see +%the effect of the filter. +%%%%%%%%%%%%%%%%%% +close all +clear all + +%%%%%%%%%%%% +%User inputs +%%%%%%%%%%%% +stepByStep = 0; %If we want a pause after each iteration (1) otherwise batch mode (0) +usePrefinedGaussians = 0; +loopThreshold = 0.3; +%%%%%%%%%%%% + +%Based on the very simple example on discrete recursive Bayes filter found +%in the green book p. 543 +%(Russel and Norvig : Artificial Intelligence - A Modern Approach) + +m = 11; %image counts in the working memory (virtual place included) +nIter = 10; %iterations +virtualPlacePrior = 0.8; + +% likelihood = rand(nIter,m); +likelihood = [0.537812527177546,0.973823826601782,0.679517492334616,0.511334385523834,0.133172784517682,0.616295123827850,0.333032553637148,0.0931269898117809,0.834956800914038,0.789846368465099,0.966949104425550,0.646502710583242;0.134060600345818,0.764640454269780,0.704121891203037,0.914142330394849,0.295008954572581,0.669364170554407,0.413416552284401,0.319027136248953,0.325143400597060,0.913520615552384,0.208804360594363,0.128169077499018;0.540947224212142,0.243683930209342,0.460884056746050,0.0919339388425310,0.166627082935154,0.0372015327510951,0.414348152562633,0.886961614706448,0.367639299837922,0.533254359985603,0.520476252773875,0.0813205028568441;0.857368192475905,1,2,8,2,1,0.414348152562633,0.413416552284401,0.794839466004007,0.804076444490502,0.225546129397670,0.659227054221110;0.198017363329941,0.137854510225166,1,2,8,2,1,0.414348152562633,0.0993087162421777,0.562660356611037,0.567197873830803,0.0273988510815916;0.155609101290637,0.629811667025549,0.0762044335388465,1,2,8,2,1,0.414348152562633,0.750876451961769,0.998163526074098,0.985179800828888;0.0613777678138677,0.857014692659853,0.444624971046257,0.785375111038118,1,2,8,2,1,0.414348152562633,0.131865336731199,0.539330947126751;0.661073988932246,0.899798333270360,0.165706296551124,0.602400257965727,0.218271272437479,1,2,8,2,1,0.414348152562633,0.373834916709097;0.0186026419313314,0.348368402231864,0.398748973477181,0.465908353449366,0.706077628393318,0.522221302087482,1,2,8,2,1,0.414348152562633;0.291102936564625,0.486310193286963,0.920584670910461,0.298131346189007,0.0390136924156214,0.567617714955188,0.901802906347484,1,2,8,2,1;]; +likelihoodNormalized = ones(nIter,m+1); + +%Initialize the prediction matrix +%first row is for the virtual place (loop closure) +%next rows are for each already visited place +prediction = zeros(m+1,m+1); +%Gaussians... +if(usePrefinedGaussians == 0) + %A-Create prediction with two gaussians + x = 1:1:m; + for i=1:m + y1 = gaussmf(x, [0.8 i-1]); + y2 = gaussmf(x, [0.8 i+1]); + y = y1+y2; + y = (y / sum(y)); + prediction(i+1,:) = [0.1 y]; + %normalize + prediction(i+1,:) = prediction(i+1,:) / sum(prediction(i+1,:)); + end + prediction(1,:) = [virtualPlacePrior ones(1,m)*0.1/(m)]; + + %Example + x=1:1:9; + y=gaussmf(x,[0.8 4])+gaussmf(x,[0.8 6]); + y=y/sum(y); + y=[0.1 y]; + y=y/sum(y); + figure(1); + plot([-1 x],y) + title(['Example of the prediction (two gaussians) for the loop closure event at 5' char(10) '(we suppose that it''s more probable to be in the next/previous place) after a loop closure.' char(10) 'Id -1 means a new place.']) + +else + %B-Create prediction using predefined values (representing approximativaly + %a sum of gaussian curves) + %(like implemented in c++, AvpdCore::BayesFilter::generatePrediction()) + %Format of predictionLC = {virtualPlace LoopCLosure neighbor-1 neighbor+1 neighbor-2 neighbor+2...} + predictionLC = [0.1 0.175 0.1 0.275 0.05 0.15 0.025 0.025]; %Forward probabilities +% predictionLC = [0.1 0.19 0.24 0.24 0.1 0.1 0.01 0.01]; %equal backward/forward + prediction = generatePrediction(virtualPlacePrior, predictionLC, m); + + %Example + x=1:1:9; + smallValue = (1-sum(predictionLC)) / length(x-5); + y = [predictionLC(1) smallValue predictionLC(7) predictionLC(5) predictionLC(3) predictionLC(2) predictionLC(4) predictionLC(6) predictionLC(8) smallValue]; + y=y/sum(y); + figure(1); + plot([-1 x],y) + title(['Example of the prediction (predefined pdf) for the loop closure event at 5' char(10) '(we suppose that it''s more probable to be in the next and/or previous place) after a loop closure.' char(10) 'Id -1 means a new place.']) +end + +%Initialize the prior with "no loop closure event" +prior = zeros(nIter+1, m+1); +prior(1,:) = prediction(1,:); + +figure(2) +subplot(211) +imagesc(prediction) +title('prediction matrix (m+1 x m+1 where m is the number of images + 1 virtual place)') +subplot(212) +imagesc(prediction') +title('prediction^T') + +statusLoop = zeros(1,nIter); + +loopClosureId = 0; +for iter=1:nIter + %reset loopClosureId + loopClosureId = 0; + + %Adjust likelihood + likelihoodNormalized(iter,:) = adjustLikelihood(likelihood(iter,:)); + + %posterior = n * likelihood * (prediction x prior) + % Note the transposed prediction' + prior(iter+1,:) = likelihoodNormalized(iter,:) .* (prediction' * prior(iter,:)')'; + + %normalize + prior(iter+1,:) = prior(iter+1,:) / sum(prior(iter+1,:)); + + % Loop closure ? + maxLoopProb = 0; + for i=2:m+1 + if prior(iter+1,i) > maxLoopProb + if i >= 3 && i <= m + if prior(iter+1,i-1)+prior(iter+1,i)+prior(iter+1,i+1) > loopThreshold + loopClosureId = i-1; + maxLoopProb = prior(iter+1,i); + end + elseif i == 2 + if prior(iter+1,i) + prior(iter+1,i+1) > loopThreshold + loopClosureId = i-1; + maxLoopProb = prior(iter+1,i); + end + else + if prior(iter+1,i-1) + prior(iter+1,i) > loopThreshold + loopClosureId = i-1; + maxLoopProb = prior(iter+1,i); + end + end + end + end + + statusLoop(iter) = loopClosureId; + + figure(3) + plot(statusLoop(1:iter), 'o'); + title(['Loop closure (image id)(iteration ' num2str(iter) ')']) + xlabel('Iteration') + ylabel('Loop closure id') + + figure(4) + subplot(411) + plot(prior(iter,:)'); + title(['prior (iteration ' num2str(iter) ')']) + subplot(412) + plot((prediction' * prior(iter,:)')'); + title('prediction^T x prior') + subplot(413) + plot(likelihoodNormalized(iter,:)); + title('likelihoodNormalized') + subplot(414) + plot(prior(iter+1,:)); + title('posterior = n * likelihood * (prediction^T x prior)') + + if(stepByStep ~=0) + disp('Press any key to continue...') + pause + end +end + +figure(5) +subplot(3,1,1) +plot(likelihood'); +title('likelihood') +subplot(3,1,2) +plot(likelihoodNormalized'); +title('likelihoodNormalized') +subplot(3,1,3) +plot(prior'); +title('posterior') + +%% We simulate here when the neighbors of id=4 don't exist... +% On loop closure, the pdf is {1 ... 1 2 3 5 3 1...} where the +% first 3 is the center... (its a forward probability) +% No loop closure, the pdf is {10 1 1 1 1 1 1....} +figure(6) +tmp=[10,1,1,1,1,1,1,1,1,1,1; + 1,6,5,4,0,0,0,0,0,0,0; + 1,3,3,9,0,0,0,0,0,0,0; + 1,1,2,12,0,0,0,0,0,0,0; + 1,0,0,0,15,0,0,0,0,0,0; + 1,0,0,0,0,6,5,3,1,0,0; + 1,0,0,0,0,3,3,5,3,1,0; + 1,0,0,0,0,1,2,3,5,3,1;]; +subplot(211) +imagesc(tmp) +title('Example of the effect of forgotten neighbors (around 5) on a prediction matrix') +subplot(212) +imagesc(tmp') +title('prediction^T') diff --git a/Matlab/RecursiveBayesCtabmap.m b/Matlab/RecursiveBayesCtabmap.m new file mode 100644 index 0000000000..135a6b1ec8 --- /dev/null +++ b/Matlab/RecursiveBayesCtabmap.m @@ -0,0 +1,121 @@ +%%%%%%%%%%%%%%%%%%%%%%%% +%In this example, we show how the resursive Bayes filtering works in RTAB-Map +%(Already Visited Place Detector). We load real data from an experiment on +%the 090206-3 dataset (in bin/data/090206-3). This example doesn't +%show the "forgotten" skill of the real algorithm implemented in RTAB-Map, so +%it doesn't show how probabilities are managed when a signature is +%forgotten. The links (parent, loop) between signatures are not shown. +% +%You can use the same script for your data. Just have the same format than +%the one used in the data text files. An other way is to use the RTAB-Map Gui, +%it provides an option to dump the working memory (Edit->Dump memory) +%directly. +%%%%%%%%%%%%%%%%%%%%%%%% + +close all +clear all + +%%%%%%%%%%%% +%User inputs +%%%%%%%%%%%% +stepByStep = 0; %If we want a pause after each iteration (1) otherwise batch mode (0) +plotPriorAfterEachIter = 1; +loopThreshold = 0.3; +predictionNP = 0.8; % prediction for "New place event" +predictionLC = [0.1 0.175 0.1 0.275 0.05 0.15 0.025 0.025]; % prediction pattern for "Loop closure event" +%%%%%%%%%%%% + +% load data +signRef = dlmread('DumpMemorySign.txt', ' ', 1, 0); + +%ignore the virtual place, it will be generated afer each iteration +signRef = signRef(2:end,:); %preallocation +nIter = size(signRef,1); + +% Initialize the prior +prior = cell(nIter,1); %preallocation +% Initialize the memory, contains only a virtual place null +memory = zeros(nIter+1,size(signRef,2)); %preallocation +memory(1,1) = -1; %id virtual place +dictionary = []; + +timeUpdateDictionary = zeros(nIter,1); %seconds +timeUpdateCommonSignature = zeros(nIter,1); %seconds +timeGetLikelihood = zeros(nIter,1); %seconds +timeAdjustLikelihood = zeros(nIter,1); %seconds +timeGeneratePrediction = zeros(nIter,1); %seconds +timeUpdatePrior = zeros(nIter,1); %seconds + +% Recursive Bayes estimation +for iter=1:nIter + % Signature creation + newSign = signRef(iter,:); %[id wordIds...] + t = cputime; + dictionary = updateDictionary(dictionary, newSign); + timeUpdateDictionary(iter) = cputime - t; + t = cputime; + + % add to memory + memory(iter+1, :) = newSign; + + % Update virtual place + [memory(1,:) dictionary] = updateCommonSignature(memory(1:iter+1,:), dictionary); + timeUpdateCommonSignature(iter) = cputime - t; + t = cputime; + + % Compute the likelihood + likelihood = computeLikelihood(memory(iter+1,:), memory(1:iter+1,:), dictionary); + %ignore the last (current signature) + likelihood = likelihood(1:end-1); + timeGetLikelihood(iter) = cputime - t; + t = cputime; + + % normalize the likelihood with std dev and mean + likelihoodNormalized = adjustLikelihood(likelihood); + timeAdjustLikelihood(iter) = cputime - t; + t = cputime; + + % generate prediction + prediction = generatePrediction(predictionNP, predictionLC, length(likelihood)-1); + timeGeneratePrediction(iter) = cputime - t; + t = cputime; + + % update the prior (recursive Bayes estimation equation) + if iter == 1 + prior{iter} = 1; % 100% probability to be in a new place + else + prior{iter} = [prior{iter-1};0]; + end + prior{iter} = likelihoodNormalized .* (prediction' * prior{iter}); + prior{iter} = prior{iter}/sum(prior{iter}); %Normalize + timeUpdatePrior(iter) = cputime - t; + t = cputime; + + if plotPriorAfterEachIter || iter == nIter + figure(1) + hold off + x = -1; + if length(prior{iter}) > 1 + x = [x 1:length(prior{iter})-1]; + end + plot(x, prior{iter}); + axis([x(1) x(end)+6 0 1]) + hold on + plot(x, ones(size(x))*loopThreshold, 'r') + title(['Prior pdf (iteration ' num2str(iter) '/' num2str(nIter) ')']) + legend('pdf', 'loop closure threshold') + end + + clc + disp(['(iteration ' num2str(iter) '/' num2str(nIter) ')']) + + if stepByStep ~= 0 && iter ~= nIter + disp('Press any key to continue...') + pause + end +end + +figure(2) +plot([timeUpdateDictionary timeUpdateCommonSignature timeGetLikelihood timeAdjustLikelihood timeGeneratePrediction timeUpdatePrior]) +title('Timings (seconds)') +legend('timeUpdateDictionary', 'timeUpdateCommonSignature', 'timeGetLikelihood', 'timeAdjustLikelihood', 'timeGeneratePrediction', 'timeUpdatePrior') \ No newline at end of file diff --git a/Matlab/ShowLogs/ShowLogs.m b/Matlab/ShowLogs/ShowLogs.m new file mode 100644 index 0000000000..d725aa4d25 --- /dev/null +++ b/Matlab/ShowLogs/ShowLogs.m @@ -0,0 +1,340 @@ + +%--------------------------------------------------------- +% MatLab script. +% This shows some informations logged by the application. +% This script may work directly with octave. +%--------------------------------------------------------- +% Just put along LogF.txt and LogI.txt files generated +% (look in the application working directory). +% The files must have the same number of lines. +% +% Dependency : importfile.m +%--------------------------------------------------------- + +%-------------------- +% Parameters +%-------------------- + +close all +clear all + +Prefix = '.'; +%Prefix = './Results'; + +DataSet = ''; +%DataSet = 'NewCollege'; +%DataSet = 'CityCentre'; +%DataSet = 'Lip6Indoor'; +%DataSet = 'Lip6Outdoor'; +%DataSet = 'Lip6Outdoor_1Hz'; +%DataSet = 'UdeS_1Hz'; + +% The Ground Truth is a squared bmp file (size must match the log files +% length) where white dots mean loop closures. +% Grey dots mean 'loop closures to ignore', this happens when the rehearsal +% doesn't match consecutive images together. +PrefixGT = '.'; +%PrefixGT = './GT'; +GroundTruthFile = [PrefixGT '/' '090206-3_GT.bmp']; +%GroundTruthFile = [PrefixGT '/' DataSet '.bmp']; + + +%--------------------------------------------------------- + +display(' '); +display('Loading log files...'); +importfile([Prefix '/' DataSet '/' 'LogF.txt']); +% COLUMN HEADERS : +% 1 totalTime +% 2 timeMemoryUpdate, +% 3 timeReactivations, +% 4 timeLikelihoodCalculation, +% 5 timePosteriorCalculation, +% 6 timeHypothesesCreation, +% 7 timeHypothesesValidation, +% 8 timeRealTimeLimitReachedProcess, +% 9 timeStatsCreation +% 10 highestHypothesisValue +% 11 vpLikelihood +% 12 maxLikelihood +% 13 sumLikelihoods +% 14 mean likelihood +% 15 stddev likelihood + +importfile([Prefix '/' DataSet '/' 'LogI.txt']); +% COLUMN HEADERS : +% 1 lcHypothesisId, +% 2 mostLikelihoodId, +% 3 signaturesRemoved, +% 4 hessianThr, +% 5 wordsNewSign, +% 6 dictionarySize, +% 7 this->getSTMem().size(), +% 8 rejectLoopReason, +% 9 processMemoryUsed, +% 10 databaseMemoryUsed +% 11 signaturesReactivated +% 12 lcHypothesisReactivated +% 13 refUniqueWordsCount +% 14 reactivateId +% 15 non nulls count + +startAt = 1; +% endAt = 1175; +endAt = length(LogF(:,1)); + +figure +H1 = plot(LogF(startAt:endAt,1)*1000); +hold on +% H2 = plot(1:length(LogF(startAt:endAt,1)), ones(length(LogF(startAt:endAt,1)),1).*mean(LogF(startAt:endAt,1))*1000, 'r-') +%title('Total process time / Location') +ylabel('Time (ms)') +xlabel('Location indexes') +meanTime = mean(LogF(startAt:endAt,1))*1000 +%plot([1 length(LogF(:,1))], [800 800], 'r') +%plot([1 length(LogF(:,1))], [1000 1000], 'k') + + +% ------------------------- +% Time details +figure +subplot(7,1,1) +plot(LogF(startAt:endAt,2)*1000) +title('timeMemoryUpdate (ms)') + +subplot(7,1,2) +plot(LogF(startAt:endAt,3)*1000) +title('timeReactivations (ms)') + +subplot(7,1,3) +plot(LogF(startAt:endAt,4)*1000) +title('timeLikelihoodCalculation (ms)') + +subplot(7,1,4) +plot(LogF(startAt:endAt,5)*1000) +title('timePosteriorCalculation (ms)') + +subplot(7,1,5) +plot(LogF(startAt:endAt,6)*1000) +title('timeHypothesesCreation (ms)') + +subplot(7,1,6) +plot(LogF(startAt:endAt,7)*1000) +title('timeHypothesesValidation (ms)') + +subplot(7,1,7) +plot(LogF(startAt:endAt,8)*1000) +title('timeStatsCreation (ms)') + +% ------------------------- +figure +plot([LogF(startAt:endAt,2) sum(LogF(startAt:endAt,2:3),2) sum(LogF(startAt:endAt,2:4),2) sum(LogF(startAt:endAt,2:5),2) sum(LogF(startAt:endAt,2:6),2) sum(LogF(startAt:endAt,2:7),2) sum(LogF(startAt:endAt,2:8),2)]); +legend('timeMemoryUpdate', 'timeReactivations', 'timeLikelihoodCalculation', 'timePosteriorCalculation', 'timeHypothesesCreation', 'timeHypothesesValidation', 'timeRealTimeLimitReachedProcess', 'timeStatsCreation') +title('Process time details') + +figure +subplot(211) +plot(LogF(startAt:endAt,3)); +title('Reactivation time') +subplot(212) +plot(LogI(:,11),'.') + +% ------------------------- +figure +subplot(211) +plot(LogI(startAt:endAt, 6)); +title('dictionary size') + +subplot(212) +plot([LogI(startAt:endAt, 9)/1000000 LogI(startAt:endAt, 10)/1000000]); +title('Memory usage (in MB)') +legend('Process', 'Database') +% ------------------------- + +figure +% subplot(211) +H1 = plot(LogI(startAt:endAt,7)); +% hold on +% H2 = plot(1:length(LogI(startAt:endAt,7)), ones(length(LogI(startAt:endAt,7)),1).*mean(LogI(startAt:endAt,7)), 'r--') +%title('Working memory size') +meanWM = mean(LogI(startAt:endAt,7)) +ylabel('WM size (locations)') +xlabel('Location indexes') +% set(H1,'color',[0.3 0.3 0.3]) +% set(H2,'color',[0 0 0]) +% subplot(212) +% plot(LogI(startAt:endAt,6)); +meanDict = mean(LogI(startAt:endAt,6)) +% ylabel('Dictionary size') +% xlabel('Location indexes') + +meanWordsPerSign = mean(LogI(startAt:endAt,5)) + + +% ------------------------- +% Detected/Accepted/Rejected loop closures + +% from VerifyEpipolarGeometry.h +% UNDEFINED, 10 +% ACCEPTED, 11 +% NO_HYPOTHESIS, 12 +% MEMORY_IS_NULL, 13 +% NOT_ENOUGH_MATCHING_PAIRS, 14 +% EPIPOLAR_CONSTRAINT_FAILED, 15 +% NULL_MATCHING_SURF_SIGNATURES 16 + +figure; +subplot(311) +plot(LogF(:,10), '.') +title('Highest posterior + lc accepted and rejected') +hold on +%rejected hypotheses +y = LogF(:,10); +x = 1:length(y); +y(LogI(startAt:endAt, 8) >= 10 & LogI(startAt:endAt, 8) <= 11) = []; +x(LogI(startAt:endAt, 8) >= 10 & LogI(startAt:endAt, 8) <= 11) = []; +plot(x,y, 'r.') +%rejected (by ratio) hypotheses +y = LogF(:,10); +x = 1:length(y); +y(LogI(startAt:endAt, 8) ~= 3) = []; +x(LogI(startAt:endAt, 8) ~= 3) = []; +plot(x,y, 'b.') +%Accepted hypotheses +y = LogF(:,10); +x = 1:length(y); +y(LogI(startAt:endAt, 8) < 10 | LogI(startAt:endAt, 8) > 11) = []; +x(LogI(startAt:endAt, 8) < 10 | LogI(startAt:endAt, 8) > 11) = []; +plot(x,y, 'g.') +subplot(312) +plot(LogI(:,2), '.') +title('Id corresponding to highest posterior + lc accepted and rejected') +hold on +%rejected hypotheses +y = LogI(:,2); +x = 1:length(y); +y(LogI(startAt:endAt, 8) >= 10 & LogI(startAt:endAt, 8) <= 11) = []; +x(LogI(startAt:endAt, 8) >= 10 & LogI(startAt:endAt, 8) <= 11) = []; +plot(x,y, 'r.') +%rejected (by ratio) hypotheses +y = LogI(:,2); +x = 1:length(y); +y(LogI(startAt:endAt, 8) ~= 3) = []; +x(LogI(startAt:endAt, 8) ~= 3) = []; +plot(x,y, 'b.') +%Accepted hypotheses +y = LogI(:,2); +x = 1:length(y); +y(LogI(startAt:endAt, 8) < 10 | LogI(startAt:endAt, 8) > 11) = []; +x(LogI(startAt:endAt, 8) < 10 | LogI(startAt:endAt, 8) > 11) = []; +plot(x,y, 'g.') +subplot(313) +plot(LogI(:,5),'.') +title('wordsNewSign') +hold on +%rejected hypotheses +y = LogI(:,5); +x = 1:length(y); +y(LogI(startAt:endAt, 8) >= 10 & LogI(startAt:endAt, 8) <= 11) = []; +x(LogI(startAt:endAt, 8) >= 10 & LogI(startAt:endAt, 8) <= 11) = []; +plot(x,y, 'r.') +%rejected (by ratio) hypotheses +y = LogI(:,5); +x = 1:length(y); +y(LogI(startAt:endAt, 8) ~= 3) = []; +x(LogI(startAt:endAt, 8) ~= 3) = []; +plot(x,y, 'b.') +%Accepted hypotheses +y = LogI(:,5); +x = 1:length(y); +y(LogI(startAt:endAt, 8) < 10 | LogI(startAt:endAt, 8) > 11) = []; +x(LogI(startAt:endAt, 8) < 10 | LogI(startAt:endAt, 8) > 11) = []; +plot(x,y, 'g.') +% %matched sign words +% y = LogI(:,2); +% x = 1:length(y); +% mask = zeros(1,length(y)); +% y(LogI(startAt:endAt, 8) ~= 11) = []; +% for i=1:length(y) +% mask(y(i)) = 1; +% end +% y = LogI(:,5); +% y(~mask) = []; +% x(~mask) = []; +% plot(x,y, 'c.') +% %matched sign words for rejected +% y = LogI(:,2); +% x = 1:length(y); +% mask = zeros(1,length(y)); +% y(LogI(startAt:endAt, 8) < 12) = []; +% for i=1:length(y) +% mask(y(i)) = 1; +% end +% y = LogI(:,5); +% y(~mask) = []; +% x(~mask) = []; +% plot(x,y, 'm.') + +lcAccepted = sum(LogI(startAt:endAt, 8) >= 10 & LogI(startAt:endAt, 8) <= 11) +lcReactivated = sum(LogI(startAt:endAt, 12) == 1) +lcRejected = sum(LogI(startAt:endAt, 8) > 11 | LogI(startAt:endAt, 8) == 3) +lcRejectedNotEnoughPairs = sum(LogI(startAt:endAt, 8) == 14) +lcRejectedEpipolarGeo = sum(LogI(startAt:endAt, 8) > 14) + +%figure; +%plot([1.0 * (LogI(startAt:endAt, 8) == 10) ... +% 1.01 * (LogI(startAt:endAt, 8) == 11) ... +% 1.02 * (LogI(startAt:endAt, 8) == 14) ... +% 1.03 * (LogI(startAt:endAt, 8) == 15)], '.'); +%title('Reject loop reason') +%legend('UNDEFINED', 'ACCEPTED', 'NOT ENOUGH MATCHING PAIRS', 'EPIPOLAR CONSTRAINT FAILED') + +% ----------------- +% Squared matrix + + + +%% +%Precision-Recall graph + +GroundTruth = []; +if exist(GroundTruthFile, 'file') + PR = getPrecisionRecall(LogI, LogF, GroundTruthFile, 0.03); + + Precision = PR(:,1); + Recall = PR(:,2); + PrecisionVerified = PR(:,3); + RecallVerified = PR(:,4); + + %plot the Precision-Recall + figure + plot([Recall RecallVerified], [Precision PrecisionVerified]) + legend('Without verification', 'With verification') + title('Precision - Recall') + xlabel('Recall (%)') + ylabel('Precision (%)') +end + +%% +% count = 0; +% for i=2:length(LogF(:,10)) +% if(LogF(i,10) > 0.45 && LogF(i,10) < LogF(i-1,10)*0.9) +% display(['i=' num2str(i) ' with=' num2str(LogI(i,2)) ' ratio=' num2str(LogF(i,10)/LogF(i-1,10))]) +% count = count +1; +% end +% end +% count + +%% +% figure +% hold on +% K=100; +% %plot(1./(K*LogF(:,15)), 'r') +% %plot(log10(1./(LogF(:,15))), 'c') +% scale=1; +% %plot(log10(1./(LogF(:,15))).^2 ./ ((LogF(:,12)-LogF(:,15))./LogF(:,14)), 'k') +% %plot(log10(1./(LogF(:,15))), 'm') +% plot((LogF(:,12)-LogF(:,15))./LogF(:,14), 'g') +% plot([0, length(LogF(:,15))], [1 1], 'k:') +% plot(LogF(:,11), 'b') +% %legend(['K=' num2str(K)], 'ln', 'ln scaled', 'log10', 'max sim', '1', 'Vp likelihood') \ No newline at end of file diff --git a/Matlab/ShowLogs/getPrecisionRecall.m b/Matlab/ShowLogs/getPrecisionRecall.m new file mode 100644 index 0000000000..680051dc33 --- /dev/null +++ b/Matlab/ShowLogs/getPrecisionRecall.m @@ -0,0 +1,109 @@ +function [ PR ] = getPrecisionRecall( LogI, LogF, GT_file, LoopThr ) +%GETPRECISIONRECALL Calculate the precision-recall results from the log +%files of RTAB-Map and a Ground Truth file (a bmp). +% PR(:,1) = Precision +% PR(:,2) = Recall +% PR(:,3) = Precision with verification +% PR(:,4) = Recall with verification +% +% LogI: The 'LogI.txt' generated file +% LogF: The 'LogF.txt' generated file +% GT_file: The related Ground truth file of the dataset ('GT.bmp') +% LoopThr: Display false positives over the loop thr (>=0.0 && < 1.0) + +GroundTruth = []; +if exist(GT_file, 'file') + display('--- getPrecisionRecall ---'); + display(['Loading GroundTruth ''' GT_file ''' ...']); + GroundTruth = imread(GT_file); +else + error(['The ground truth ''' GT_file '''doesn''t exist.']) +end + +if ~isempty(GroundTruth) + %display('Calculating Precision-Recall graph') + %figure + %imshow(GroundTruth) + %title('GroundTruth') + + if size(GroundTruth, 1) ~= length(LogF(:,1)) || size(GroundTruth, 1) ~= length(LogI(:,1)) + error(['The ground truth size doesn''t match the log files (LogI=' num2str(length(LogI(:,1))) ', LogF=' num2str(length(LogF(:,1))) ', GT=' num2str(size(GroundTruth, 1)) ')']) + end + + + %[highestHypot, CorrespondingID, GT, Accepted, Good, Index, UnderLoopRatio] descending order + lc = [LogF(:,10) LogI(:,2) sum(GroundTruth == 255, 2)>0 (LogI(:, 8) == 10 | LogI(:, 8) == 11) zeros(length(LogI(:,1)),1) (1:length(LogF(:,10)))' LogI(:, 8) == 3]; + + %eliminate loops on diagonal + ignored = 0; + for i=1:length(lc) + index = find(GroundTruth(:,i) > 0 & GroundTruth(:,i) < 255); + if ~isempty(index) + row = GroundTruth(index(1), :); + if lc(i,2) >= min(index) && lc(i,2) <= max(index) + display(['i=' NUM2STR(i) ' loop=' NUM2STR(LogI(i,2)) ' min(index)=' NUM2STR(min(index)) ' max(index)' NUM2STR(max(index))]) + lc(i,1) = 0; + ignored = ignored + 1; + end + end + end + + lc = sortrows(lc, -1); + + GT_total_positives = sum(sum(GroundTruth == 255, 2) > 0) + + %figure + %plot(sum(GroundTruth > 0, 2)>0) + %title('Ground truth (timeline)') + + sizeNonZero = sum(lc(:,1) > 0); + + PR = zeros(sizeNonZero, 4); + for i=1:length(lc) + if lc(i,1) == 0 + break; + end + + id = lc(i,2); + + if id && sum(GroundTruth(lc(i,6), id)) > 0 + lc(i,5) = 1; + end + + PR(i,2) = sum(lc(1:i,5) & ~lc(1:i,7) & lc(1:i,2))/GT_total_positives; + PR(i,1) = sum(lc(1:i,5) & ~lc(1:i,7) & lc(1:i,2)) / sum(~lc(1:i, 7) & lc(1:i,2)); + + %PR(i,2) = sum(lc(1:i,5))/GT_total_positives; + %PR(i,1) = sum(lc(1:i,5)) / i; + + PR(i,4) = sum(lc(1:i,4) & lc(1:i,5))/GT_total_positives; + PR(i,3) = sum(lc(1:i,4) & lc(1:i,5)) / sum(lc(1:i,4)); + + if ~lc(i,5) && ~lc(i,7) && id && lc(i,1) >= LoopThr + display(['False positive! id=' num2str(lc(i,6)) ' with old=' num2str(id) ' (p=' num2str(lc(i,1)) ')'] ) + end + + if lc(i,4) ~= lc(i,5) && lc(i,4) + display(['False positive! (v) id=' num2str(lc(i,6)) ' with old=' num2str(id) ' (p=' num2str(lc(i,1)) ')'] ) + end + end + + index = find(PR(:,1) == 1); + if ~isempty(index) + maxRecall = PR(index(end),2) * 100; + display(['Recall max (Precision=100%) = ' num2str(maxRecall) '% (p=' num2str(lc(index(end),1)) '), accepted=' num2str(sum(lc(1:index(end),5) & ~lc(1:index(end),7) & lc(1:index(end),2)))]) + else + display('Recall max (Precision=100%) = 0') + end + indexV = find(PR(:,3) == 1); + if ~isempty(indexV) + maxRecallVerified = PR(indexV(end),4) * 100; + display(['Recall max (Precision=100%, with verification) = ' num2str(maxRecallVerified) '% (p=' num2str(lc(indexV(end),1)) ')']) + else + display('Recall max (Precision=100%, with verification) = 0') + end + display(['ignored = ' num2str(ignored)]) +end + +end + diff --git a/Matlab/ShowLogs/importfile.m b/Matlab/ShowLogs/importfile.m new file mode 100644 index 0000000000..179dc97bcb --- /dev/null +++ b/Matlab/ShowLogs/importfile.m @@ -0,0 +1,22 @@ +function importfile(fileToRead1) +%IMPORTFILE(FILETOREAD1) +% Imports data from the specified file +% FILETOREAD1: file to read + +% Auto-generated by MATLAB on 07-Oct-2009 10:10:26 + +% Import the file +rawData1 = importdata(fileToRead1); + +% For some simple files (such as a CSV or JPEG files), IMPORTDATA might +% return a simple array. If so, generate a structure so that the output +% matches that from the Import Wizard. +[unused,name] = fileparts(fileToRead1); %#ok +newData1.(genvarname(name)) = rawData1; + +% Create new variables in the base workspace from those fields. +vars = fieldnames(newData1); +for i = 1:length(vars) + assignin('base', vars{i}, newData1.(vars{i})); +end + diff --git a/Matlab/ShowLogs/logaHeaders.txt b/Matlab/ShowLogs/logaHeaders.txt new file mode 100644 index 0000000000..483352a4a0 --- /dev/null +++ b/Matlab/ShowLogs/logaHeaders.txt @@ -0,0 +1,17 @@ +fprintf(_foutFloat, "%f %f %f %f %f %f %f %f %f\n", + totalTime, + timeMemoryUpdate, + timeReactivations, + timeLikelihoodCalculation, + timePosteriorCalculation, + timeHypothesesCreation, + timeHypothesesValidation, + timeRealTimeLimitReachedProcess, + timeStatsCreation); +fprintf(_foutInt, "%d %d %d %d\n", + lcHypothesisId, + mostLikelihoodId, + signaturesRemoved, + hessianThr, + wordsNewSign, + dictionarySize); diff --git a/Matlab/Tests/090306-3_db-Dictionary.txt b/Matlab/Tests/090306-3_db-Dictionary.txt new file mode 100644 index 0000000000..a540a22962 --- /dev/null +++ b/Matlab/Tests/090306-3_db-Dictionary.txt @@ -0,0 +1,4555 @@ +WordID SignaturesID... +1 1 +2 1 4 29 +3 1 18 25 58 58 58 58 +4 1 1 11 12 21 24 28 37 68 76 +5 -1 1 11 12 12 15 25 25 36 37 52 65 68 76 76 +6 1 +7 1 1 1 8 12 34 41 47 56 73 +8 -1 1 1 1 2 2 2 3 4 4 5 10 11 11 11 13 13 16 16 16 18 18 20 20 21 36 39 40 40 41 42 43 43 69 76 81 81 +9 -1 1 2 2 5 5 7 8 8 10 12 13 13 15 15 15 15 15 16 16 16 20 20 21 22 22 23 39 42 46 49 50 50 50 50 50 51 54 54 55 55 55 55 55 55 55 55 55 55 56 56 57 57 57 58 79 79 81 82 +10 1 20 24 48 +11 1 +12 1 12 28 31 40 +13 1 15 20 35 64 71 75 +14 1 +15 1 9 11 25 32 41 42 76 +16 1 41 81 +17 1 40 68 +18 1 2 2 5 13 24 41 42 53 73 75 +19 1 1 2 2 28 32 36 40 41 42 +20 1 +21 -1 1 2 2 2 2 16 16 28 39 39 41 41 41 41 42 56 72 79 79 79 80 81 +22 -1 1 3 18 20 20 21 22 24 27 30 33 34 34 43 59 61 61 62 66 74 75 75 76 +23 -1 1 1 1 1 2 2 2 4 6 6 8 10 10 11 12 16 18 18 18 20 24 25 26 26 28 30 30 31 32 33 34 35 35 36 37 39 40 40 40 41 41 42 45 50 50 51 52 55 58 66 67 68 69 72 72 73 75 79 79 80 81 +24 1 2 3 40 41 42 43 61 77 80 81 82 +25 1 +26 1 2 40 41 +27 -1 1 1 2 2 2 14 24 39 40 41 54 69 72 76 77 78 80 81 82 +28 1 18 18 40 77 80 81 +29 1 6 7 10 10 11 12 32 35 71 74 +30 -1 1 1 3 5 5 6 6 7 10 10 10 10 11 11 12 12 13 15 16 16 16 16 16 16 22 25 29 30 33 35 37 39 40 45 47 49 49 49 50 51 51 53 55 56 57 59 59 62 66 68 69 71 71 73 75 77 81 +31 1 1 40 +32 -1 1 1 2 3 5 5 5 8 8 8 8 9 9 9 10 10 11 12 12 12 12 13 16 23 24 25 25 29 30 30 32 32 33 34 34 35 36 37 37 39 40 41 41 45 45 48 48 49 49 49 51 51 53 56 56 57 62 63 63 65 66 67 68 71 72 73 74 75 75 75 76 79 80 81 +33 1 80 +34 -1 1 18 24 26 27 29 31 32 36 37 40 41 52 60 65 66 75 +35 1 +36 -1 1 2 2 10 19 21 21 24 25 32 32 39 41 47 56 66 75 79 +37 1 4 22 52 82 +38 1 31 40 81 +39 1 2 40 41 80 81 82 +40 -1 1 2 3 5 6 10 25 27 30 58 59 70 81 82 +41 1 +42 1 40 +43 1 81 +44 1 +45 -1 1 5 7 12 16 18 24 24 26 26 27 29 40 41 64 79 81 82 +46 1 1 2 39 40 41 80 81 82 +47 1 24 39 41 79 +48 1 39 72 +49 1 1 1 7 31 40 81 +50 1 +51 1 18 18 18 24 24 33 40 50 81 +52 1 25 35 36 37 40 65 +53 -1 1 1 7 11 18 24 26 27 29 37 39 40 40 40 45 49 49 58 59 65 67 72 74 80 80 +54 1 1 31 40 81 +55 1 81 +56 1 +57 1 +58 1 +59 1 +60 1 41 +61 -1 1 1 2 2 2 11 24 24 26 26 28 31 32 32 45 50 50 51 53 64 66 73 76 76 77 +62 -1 1 2 2 3 39 39 40 40 41 42 57 70 80 80 80 81 +63 1 2 3 12 18 19 59 79 80 +64 -1 1 2 3 4 7 10 12 15 15 15 16 16 23 39 41 42 42 47 51 55 56 58 60 80 82 82 +65 1 2 35 40 62 81 +66 1 31 40 80 81 +67 -1 1 2 2 2 3 3 3 4 4 4 4 4 4 4 7 8 8 8 8 9 10 10 11 11 11 12 13 13 13 13 14 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 20 20 20 20 20 21 21 22 22 22 22 22 22 23 23 37 37 39 39 39 39 42 42 43 45 47 47 47 48 49 50 53 53 53 54 54 54 54 55 55 55 55 55 56 56 56 56 56 56 56 56 56 56 56 56 56 56 57 57 57 57 57 57 57 57 57 57 57 59 59 59 60 61 61 61 62 62 63 67 67 70 75 79 79 79 79 79 79 79 79 79 79 80 82 82 +68 -1 1 2 2 2 39 39 40 41 42 79 80 81 82 82 +69 1 2 2 40 40 41 80 81 82 82 +70 1 39 39 40 79 81 +71 1 1 39 39 40 79 80 80 +72 1 2 37 77 +73 1 40 80 +74 1 4 45 69 79 80 81 +75 1 +76 1 40 41 81 +77 -1 1 5 5 7 11 26 32 33 36 39 40 45 68 71 72 76 +78 1 +79 1 1 2 2 36 41 42 80 82 82 +80 1 2 41 81 +81 1 2 3 32 39 40 41 42 69 80 81 82 +82 1 +83 1 20 20 32 51 59 80 +84 1 79 80 +85 -1 1 1 3 3 4 8 11 11 11 11 11 13 21 27 39 42 43 59 62 79 80 81 82 +86 -1 1 1 2 11 22 25 31 32 37 39 39 39 40 41 51 62 79 80 81 +87 1 2 39 39 40 41 79 80 81 82 +88 1 39 40 79 80 81 81 81 +89 1 39 +90 1 39 40 40 79 +91 1 39 40 79 +92 1 40 +93 1 40 +94 1 40 81 +95 1 2 8 15 23 28 39 40 41 80 81 82 +96 1 40 +97 -1 1 2 3 7 14 26 32 39 39 39 47 48 49 60 79 +98 -1 1 3 3 4 5 9 12 29 30 31 39 40 51 52 62 65 68 70 71 79 81 +99 -1 1 18 20 22 23 39 39 40 59 60 61 79 80 80 81 81 +100 -1 1 6 14 18 34 35 35 36 37 39 40 40 76 79 80 81 +101 1 39 +102 1 +103 -1 1 30 33 35 35 36 37 39 58 73 73 74 75 79 79 80 +104 1 +105 1 1 52 52 +106 -1 1 5 7 12 13 21 31 33 34 35 35 37 38 38 40 52 75 76 77 80 81 +107 1 39 40 41 80 +108 1 5 13 30 39 40 45 80 +109 1 1 39 40 41 81 81 82 +110 1 40 81 +111 1 1 +112 1 40 +113 1 2 39 40 41 79 80 81 82 +114 1 40 +115 1 82 +116 2 +117 2 29 82 +118 2 11 15 23 27 28 42 55 60 61 73 +119 2 +120 2 11 58 +121 2 5 8 19 19 21 22 35 36 37 69 71 +122 -1 2 2 3 5 5 5 5 5 5 7 9 18 24 43 45 47 63 64 68 82 +123 2 +124 -1 2 2 4 4 8 8 12 13 13 13 13 21 21 21 21 22 23 28 32 42 42 47 48 51 51 51 53 53 58 59 59 59 60 60 61 61 64 65 71 73 77 80 80 80 82 +125 -1 2 4 4 5 7 8 10 10 11 12 13 15 15 15 15 15 16 16 16 16 16 16 16 16 18 21 35 36 39 39 39 40 40 41 41 42 42 43 49 51 54 55 55 55 55 56 56 56 56 56 56 56 56 57 57 60 76 77 77 77 77 77 80 +126 2 47 63 69 +127 -1 2 3 3 4 4 9 12 12 20 30 31 36 42 42 47 49 52 53 53 54 59 59 60 61 70 71 82 +128 2 +129 -1 2 3 3 3 3 4 6 6 7 7 7 7 7 8 8 10 10 15 15 20 21 23 27 32 42 45 50 56 56 56 57 58 58 58 66 66 69 70 71 72 72 77 78 +130 2 5 7 8 10 12 16 16 32 37 79 +131 -1 2 3 4 4 12 13 19 24 26 32 42 42 52 65 76 +132 2 3 4 4 +133 2 11 19 25 41 42 43 53 +134 2 20 20 22 42 42 59 61 +135 2 82 82 +136 2 +137 2 2 81 +138 2 42 +139 2 82 +140 2 43 43 +141 2 38 +142 2 2 3 6 6 6 9 11 19 22 82 +143 -1 2 3 4 6 18 19 19 21 22 28 32 33 35 37 39 40 43 46 65 67 72 81 82 +144 2 42 +145 2 82 +146 2 +147 2 +148 2 21 28 41 +149 2 +150 2 +151 2 71 +152 2 41 +153 2 2 +154 2 +155 2 3 78 +156 2 2 41 +157 -1 2 4 5 5 18 24 26 34 35 39 41 48 49 52 54 65 76 79 82 +158 2 +159 2 3 4 41 42 43 82 +160 2 +161 -1 2 4 4 10 16 16 16 26 39 41 42 43 56 57 66 79 82 +162 2 +163 2 3 41 42 +164 -1 2 15 20 22 22 26 26 26 27 31 41 45 57 57 66 +165 2 +166 2 +167 2 +168 -1 2 5 6 12 15 18 18 19 19 22 26 27 28 32 36 39 53 54 57 59 60 61 61 67 75 82 +169 -1 2 31 31 32 32 39 40 40 53 71 72 73 81 +170 2 5 22 22 31 39 40 41 47 62 79 +171 2 +172 -1 2 10 10 10 12 12 13 14 14 15 15 15 15 16 16 16 16 16 16 16 19 19 22 22 27 51 54 54 55 55 55 55 55 55 56 56 56 56 56 56 56 57 57 59 60 63 63 72 80 +173 2 18 41 69 70 +174 2 +175 -1 2 4 4 4 6 7 8 8 9 10 11 11 12 12 12 12 12 12 13 15 16 16 16 20 21 21 22 22 37 37 39 40 40 42 45 47 50 50 52 53 55 55 55 55 56 56 56 56 56 56 58 58 58 58 58 59 59 60 61 62 63 63 66 70 79 79 79 79 +176 2 +177 2 41 +178 2 +179 2 +180 2 +181 -1 2 3 3 3 4 5 6 7 7 7 7 7 7 7 8 8 8 8 9 10 10 11 15 15 15 15 15 21 23 40 41 45 47 47 49 49 49 50 51 51 53 55 55 55 56 56 56 56 56 56 56 56 57 58 59 80 82 +182 -1 2 2 4 5 8 8 11 11 18 20 23 25 25 26 26 33 34 37 39 39 41 41 52 52 55 60 65 65 67 67 68 74 76 79 81 81 +183 2 41 47 +184 2 8 33 40 45 47 47 +185 2 +186 2 4 20 39 43 80 +187 -1 2 4 4 10 11 12 18 18 18 18 19 24 28 29 30 30 33 35 36 37 41 41 47 47 47 47 47 51 60 66 69 77 81 +188 2 20 24 25 42 72 81 82 82 +189 2 8 39 +190 2 8 41 42 47 56 79 81 81 +191 2 41 82 +192 2 40 41 69 81 82 +193 2 41 +194 2 +195 2 +196 -1 2 8 10 24 25 28 30 33 34 34 34 41 42 48 64 65 65 67 72 73 74 75 78 79 80 81 81 82 +197 -1 2 2 8 8 10 12 25 31 32 39 39 41 43 53 59 76 79 80 82 +198 2 3 41 41 41 +199 2 15 16 16 39 40 41 41 55 60 79 +200 2 +201 2 4 80 +202 2 32 41 71 +203 2 41 81 +204 2 +205 2 +206 2 11 12 12 18 30 31 32 41 52 65 71 +207 2 40 +208 -1 2 4 8 13 39 40 40 41 42 48 60 64 79 80 81 82 82 +209 2 3 +210 -1 2 4 8 10 10 11 20 24 25 26 32 39 43 48 50 50 52 52 65 77 +211 2 34 34 35 82 +212 2 5 37 37 41 +213 2 39 39 40 79 79 80 82 +214 2 41 +215 2 13 26 31 36 36 +216 2 41 +217 -1 3 3 3 4 4 6 6 6 7 7 7 13 22 25 27 27 31 46 52 53 81 +218 -1 3 3 4 4 4 4 5 5 9 9 13 13 15 19 20 21 24 25 25 25 34 34 36 36 37 43 45 45 45 46 46 49 51 52 52 52 53 53 55 60 65 66 73 75 77 +219 -1 3 4 5 11 16 19 20 26 39 40 42 42 50 56 75 81 82 +220 -1 3 4 4 4 4 10 15 16 16 18 22 24 33 36 36 48 51 71 75 76 77 +221 3 3 4 4 7 7 8 10 43 47 78 +222 3 3 +223 3 43 +224 3 +225 3 4 42 +226 3 +227 3 3 4 43 +228 3 4 7 27 33 36 36 39 40 42 +229 3 3 +230 3 4 54 77 +231 3 7 13 41 47 49 52 53 54 78 81 +232 3 +233 3 25 33 +234 -1 3 4 4 4 4 4 4 4 5 5 5 7 7 8 8 8 9 10 10 10 10 10 11 12 12 13 15 16 16 16 16 16 23 23 26 27 28 36 36 36 37 39 41 42 42 43 43 46 46 46 46 47 48 49 49 50 55 55 55 55 55 56 56 56 56 56 56 57 58 61 63 66 70 71 72 75 76 77 81 +235 -1 3 9 10 10 11 13 22 24 25 28 29 30 30 31 32 33 36 36 36 37 40 43 53 55 55 55 55 55 55 55 55 63 65 72 72 73 77 79 79 80 +236 3 4 22 37 76 +237 -1 3 7 10 11 22 29 42 43 50 64 67 74 75 +238 3 3 19 25 65 +239 3 19 34 38 41 55 56 82 +240 3 +241 3 4 39 41 42 43 54 61 69 80 82 +242 3 3 4 +243 3 43 +244 -1 3 3 3 4 4 4 4 4 8 9 9 9 9 10 10 10 11 11 12 12 12 12 14 18 21 31 36 42 43 43 48 51 52 54 59 60 61 77 +245 3 4 22 36 51 59 70 +246 3 62 +247 3 4 6 7 9 12 41 42 53 75 77 82 +248 3 62 +249 3 +250 3 21 32 +251 3 11 +252 -1 3 4 5 5 11 26 35 36 36 36 37 41 42 53 66 75 75 76 76 +253 3 +254 3 +255 3 40 41 42 81 +256 3 3 43 82 +257 3 5 5 6 6 13 63 82 +258 3 40 42 80 82 +259 3 +260 3 +261 3 4 42 +262 3 +263 3 +264 3 77 +265 4 +266 4 43 53 +267 4 4 16 66 +268 4 +269 4 4 4 5 13 33 46 +270 4 +271 4 +272 4 +273 4 +274 4 +275 4 +276 4 +277 4 +278 4 43 43 +279 4 +280 4 58 +281 4 +282 4 +283 4 4 43 +284 4 +285 4 7 13 20 35 43 46 +286 -1 4 4 5 13 24 24 25 25 27 29 32 50 53 53 59 63 66 73 81 +287 4 +288 4 +289 4 +290 4 +291 4 12 32 43 59 72 +292 4 +293 4 76 +294 4 +295 4 +296 4 +297 4 +298 4 +299 4 29 66 +300 4 30 +301 4 18 20 33 43 +302 4 4 43 43 +303 4 4 29 43 78 +304 4 4 5 8 +305 4 13 13 43 43 51 51 65 +306 4 4 +307 -1 4 5 5 6 6 6 7 9 15 16 20 22 23 23 25 27 31 36 46 47 48 55 55 63 66 67 73 75 77 78 82 +308 -1 4 4 8 8 9 9 10 10 11 11 11 11 11 11 11 11 12 12 12 12 12 13 20 20 20 39 43 43 48 50 50 50 50 50 50 51 51 51 51 51 52 52 52 52 59 59 60 60 60 77 77 81 +309 4 7 +310 -1 4 4 8 9 9 11 16 16 19 20 22 26 32 35 36 37 42 42 42 42 42 42 43 47 48 48 48 57 76 77 82 +311 4 4 46 +312 4 14 19 24 24 31 54 54 +313 4 +314 4 7 21 25 35 43 59 61 63 71 73 +315 -1 4 9 9 11 24 37 37 43 43 48 51 59 59 +316 4 16 16 24 37 42 42 +317 4 +318 4 +319 4 +320 4 10 13 +321 4 +322 4 +323 4 +324 4 4 +325 4 6 6 7 32 +326 4 +327 -1 4 6 9 10 15 15 16 16 16 23 24 45 49 49 61 61 62 66 +328 -1 4 4 8 9 9 10 10 15 16 22 48 56 57 57 58 +329 4 8 8 9 13 14 37 52 +330 4 8 13 14 29 31 +331 4 +332 4 +333 4 +334 4 4 4 13 43 +335 4 43 +336 4 +337 4 43 +338 4 10 10 10 10 59 +339 4 43 43 +340 4 20 32 43 43 51 77 80 +341 4 +342 4 40 43 +343 4 4 8 10 11 16 16 16 26 55 79 82 +344 4 4 12 38 42 43 43 49 61 66 +345 4 26 +346 -1 4 4 5 6 6 8 10 15 15 15 16 16 16 20 21 34 36 41 43 45 46 47 48 49 49 50 55 55 55 56 56 57 61 61 +347 -1 4 10 10 18 27 28 32 36 40 41 43 49 50 53 54 59 67 69 81 +348 4 +349 4 +350 -1 4 4 4 4 11 25 25 29 42 43 43 61 65 +351 4 12 28 29 43 43 +352 4 +353 4 8 58 +354 4 +355 -1 4 5 5 7 8 8 11 15 15 15 16 16 16 20 21 22 22 23 41 42 42 45 47 47 47 48 48 55 55 55 55 55 55 56 57 57 58 59 61 61 62 63 67 68 +356 4 51 +357 4 5 34 39 45 76 79 +358 4 40 43 43 48 +359 4 +360 4 +361 4 +362 -1 4 8 8 8 9 10 12 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 19 20 22 22 23 23 23 25 26 36 37 37 42 42 42 43 47 47 48 48 48 48 49 49 49 50 50 50 54 55 55 55 57 57 57 59 60 61 62 62 63 63 76 77 +363 4 7 8 16 43 +364 5 +365 5 +366 5 +367 5 8 21 24 29 32 35 59 +368 5 +369 5 5 6 6 76 +370 5 6 7 21 38 65 73 +371 5 6 45 +372 -1 5 5 6 6 6 6 6 7 7 7 8 12 14 15 15 16 18 25 27 27 28 32 34 36 39 43 46 46 47 55 68 +373 -1 5 6 9 10 10 11 11 12 14 15 15 15 16 16 16 16 16 16 16 18 20 21 22 24 25 26 27 28 32 37 45 45 45 45 45 45 47 50 53 54 55 56 56 57 57 57 57 58 58 58 58 58 58 58 63 77 +374 5 48 67 +375 5 45 47 +376 5 +377 5 6 9 45 47 +378 5 8 50 +379 5 6 +380 5 32 45 45 +381 5 45 +382 5 5 6 7 28 46 +383 5 +384 5 10 35 45 48 65 65 +385 5 6 7 45 +386 5 5 +387 -1 5 6 7 8 8 9 11 13 18 19 19 28 30 47 48 48 50 65 71 +388 5 +389 5 9 10 12 13 45 50 52 +390 5 +391 5 +392 5 72 +393 -1 5 5 6 7 7 8 9 9 45 45 46 46 47 48 +394 -1 5 6 13 13 13 16 18 18 20 22 24 24 24 25 26 29 29 29 29 32 32 33 34 37 37 39 43 47 48 48 52 53 53 53 53 53 53 53 55 55 55 55 59 64 67 69 73 76 76 77 77 78 79 80 +395 5 46 64 71 +396 5 5 6 9 14 18 45 46 49 69 77 78 +397 5 9 35 45 49 65 72 78 +398 -1 5 6 9 9 18 21 27 31 35 37 40 47 66 70 +399 5 11 32 41 +400 5 7 9 26 28 35 45 68 68 +401 5 +402 5 6 21 29 43 58 74 +403 5 +404 -1 5 5 6 9 9 9 12 14 24 25 25 37 39 45 45 48 49 64 64 65 +405 5 6 45 +406 5 +407 5 45 +408 5 8 8 9 +409 5 5 6 45 +410 5 11 45 48 49 +411 5 +412 -1 5 6 7 10 11 11 11 14 14 14 15 15 22 22 35 45 46 46 50 50 50 50 56 57 63 63 +413 5 9 23 36 54 55 59 65 67 +414 5 8 8 45 45 49 +415 5 +416 5 7 34 45 47 48 +417 5 6 45 +418 5 6 7 45 46 48 +419 5 6 45 58 69 +420 5 5 45 79 +421 5 +422 5 +423 -1 5 5 5 5 7 8 11 12 18 27 34 35 35 48 48 51 52 73 74 74 +424 5 21 +425 5 +426 5 5 7 18 27 37 37 69 76 76 76 +427 5 8 14 20 26 27 28 32 37 65 76 +428 -1 5 6 11 18 27 29 30 33 34 34 45 67 73 74 +429 -1 5 5 7 9 11 12 24 24 25 26 27 37 37 37 39 45 47 51 70 70 72 72 76 77 79 +430 5 6 8 24 25 45 46 64 +431 5 +432 5 +433 5 +434 5 46 +435 5 +436 5 5 66 +437 5 +438 5 6 7 7 11 45 69 +439 5 45 47 47 58 +440 5 48 +441 -1 5 9 10 11 11 12 13 13 46 47 48 50 50 51 52 53 53 53 54 +442 -1 5 5 6 6 7 7 7 8 10 45 46 46 46 47 48 54 +443 5 46 +444 5 5 12 34 35 36 37 45 73 74 75 76 +445 5 45 46 +446 5 24 45 65 69 +447 5 45 +448 5 8 9 12 13 13 47 52 68 +449 5 +450 5 8 45 45 +451 5 6 +452 5 +453 5 6 45 64 +454 5 46 +455 5 +456 -1 5 5 6 6 6 7 9 27 28 34 43 45 45 46 46 51 58 66 67 73 74 74 76 77 +457 5 5 7 8 25 26 47 51 65 66 66 +458 5 22 43 64 70 +459 5 +460 5 7 +461 5 5 +462 5 45 +463 5 6 7 +464 5 5 22 36 +465 -1 5 13 27 28 28 30 37 45 51 53 65 65 67 68 75 +466 5 6 45 +467 5 6 7 7 8 9 10 45 46 47 48 +468 5 +469 5 5 +470 -1 5 8 8 9 9 34 34 35 35 36 36 36 37 37 64 75 +471 5 13 64 66 72 +472 5 +473 5 +474 5 45 +475 5 +476 -1 5 12 13 15 15 24 24 26 36 37 53 53 64 64 78 +477 5 25 66 66 +478 5 +479 5 +480 5 9 45 +481 5 26 66 +482 5 +483 5 +484 5 +485 5 +486 6 7 8 10 13 18 19 41 50 58 65 72 +487 6 +488 6 +489 6 46 +490 6 7 +491 6 +492 6 7 45 +493 6 7 9 46 +494 6 +495 -1 6 8 10 10 11 12 14 21 45 48 49 50 51 +496 -1 6 26 28 32 32 34 34 38 65 67 68 69 70 74 78 +497 6 +498 -1 6 6 7 8 9 12 12 14 16 45 46 46 47 50 51 51 52 52 54 +499 6 7 61 77 +500 6 49 +501 6 6 33 46 +502 6 +503 6 45 46 +504 6 7 8 25 26 26 27 29 29 47 65 +505 6 +506 -1 6 7 7 8 13 14 20 26 26 46 58 58 65 +507 6 29 30 46 +508 6 7 7 +509 6 16 16 28 28 50 +510 6 8 48 +511 6 7 8 46 46 47 +512 6 11 45 +513 6 18 25 26 27 45 74 +514 6 29 30 33 70 +515 6 10 46 +516 6 7 8 45 46 48 +517 6 46 55 +518 6 46 +519 6 45 +520 -1 6 8 10 12 13 24 25 26 26 27 31 35 38 39 47 58 75 +521 6 +522 6 +523 6 +524 6 46 +525 6 34 36 74 +526 6 7 46 +527 6 +528 6 10 45 +529 6 45 +530 6 +531 6 6 46 +532 6 7 +533 6 10 49 65 +534 6 7 46 +535 6 45 +536 6 +537 6 67 +538 6 +539 7 15 18 26 30 40 46 49 58 70 71 75 +540 7 21 59 65 76 +541 7 +542 7 37 41 46 +543 7 12 39 +544 7 +545 7 13 28 +546 7 46 +547 7 +548 7 +549 7 +550 7 12 +551 7 8 11 47 48 49 +552 7 12 24 45 50 53 +553 7 +554 7 27 28 35 37 42 46 71 +555 7 8 11 47 +556 7 33 33 46 78 +557 7 47 +558 -1 7 8 8 8 9 11 11 11 12 12 13 14 14 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 20 20 21 21 21 22 23 29 32 36 37 38 40 40 42 45 46 47 47 47 47 47 47 47 49 51 51 51 54 54 54 55 55 55 55 55 56 56 56 56 56 56 56 56 57 57 57 57 58 58 59 60 60 60 61 61 62 63 63 63 63 77 79 79 80 81 82 +559 7 47 53 +560 7 +561 7 47 53 +562 7 47 +563 7 +564 7 7 46 47 +565 7 45 46 47 +566 7 31 46 +567 7 46 47 +568 7 +569 7 46 +570 7 +571 7 15 46 +572 -1 7 8 8 8 9 9 10 47 48 48 49 49 59 73 73 +573 7 31 +574 7 74 +575 7 +576 7 8 47 +577 7 47 +578 7 +579 7 +580 7 30 72 +581 7 46 +582 7 8 8 46 48 +583 7 +584 -1 7 8 8 9 11 11 12 13 13 15 15 15 16 16 16 16 19 21 21 21 28 34 35 45 47 48 52 53 53 54 55 56 56 57 57 61 67 68 76 78 +585 -1 7 7 11 13 15 15 16 24 33 40 46 47 48 50 53 61 63 63 65 70 70 71 71 +586 7 46 +587 -1 7 18 19 20 20 21 27 30 55 58 58 60 61 69 77 82 +588 7 46 +589 7 9 10 11 18 22 46 46 48 49 80 +590 7 15 46 46 +591 7 +592 7 40 +593 7 32 45 47 48 +594 7 +595 7 45 +596 -1 7 7 10 10 13 13 20 30 35 35 36 50 69 +597 7 9 10 13 35 36 52 59 74 +598 7 +599 7 46 +600 7 30 36 64 65 68 +601 7 +602 7 47 +603 7 +604 7 8 +605 7 46 +606 7 46 +607 7 7 11 50 +608 7 11 25 30 +609 7 7 19 +610 7 47 +611 7 +612 7 +613 7 10 10 11 46 46 54 54 77 78 +614 7 46 +615 8 9 9 11 36 51 72 +616 8 48 +617 8 +618 8 33 34 74 +619 8 9 9 47 48 +620 -1 8 9 9 10 10 11 11 25 26 30 31 35 47 48 49 65 66 70 72 +621 8 9 9 24 26 26 27 47 48 51 65 66 +622 8 +623 8 +624 8 +625 8 47 +626 8 9 13 48 49 +627 8 46 49 +628 8 8 9 48 +629 8 +630 8 +631 8 81 +632 8 +633 8 +634 -1 8 9 9 10 20 28 31 32 33 34 48 65 65 +635 8 +636 8 9 10 48 49 +637 -1 8 10 11 11 12 12 13 13 13 14 20 20 47 48 48 50 51 52 52 54 55 +638 8 47 +639 8 46 47 +640 8 9 48 48 +641 8 +642 8 45 77 +643 -1 8 10 12 12 20 23 26 36 36 47 51 51 51 53 53 59 62 62 62 63 66 69 70 80 82 +644 8 9 48 49 +645 8 47 +646 -1 8 10 11 11 11 12 45 47 47 48 48 49 50 61 +647 -1 8 11 13 15 19 21 24 31 47 51 53 53 53 61 69 +648 8 +649 8 11 45 47 50 +650 8 78 +651 8 9 10 12 47 48 50 51 +652 8 11 19 20 60 62 +653 8 9 48 48 58 +654 8 47 48 +655 8 +656 8 9 9 11 12 47 48 49 +657 8 50 +658 -1 8 12 15 15 15 15 23 23 34 47 47 47 47 48 48 51 55 56 56 57 58 65 77 78 +659 8 9 +660 8 9 34 47 48 73 74 74 +661 8 +662 8 9 49 49 49 +663 8 27 67 +664 8 8 9 9 25 43 +665 8 9 47 +666 8 9 47 +667 -1 8 12 13 14 15 18 20 20 20 21 21 21 21 21 22 22 23 23 30 45 54 55 55 55 59 59 59 60 60 61 62 62 62 +668 -1 8 9 9 24 35 45 47 48 51 51 51 52 58 76 +669 8 48 +670 8 +671 8 +672 8 9 48 +673 8 +674 8 18 47 48 +675 8 47 48 48 52 +676 8 +677 8 +678 8 47 48 +679 8 64 +680 8 9 16 16 19 38 45 50 58 59 78 +681 -1 8 8 10 12 12 13 13 20 21 22 40 42 42 51 77 80 80 80 +682 8 9 47 47 48 49 +683 8 50 +684 8 +685 8 10 10 10 10 10 13 13 42 50 +686 8 60 +687 8 47 47 48 48 +688 8 +689 8 8 47 48 48 +690 8 +691 8 47 47 48 +692 8 +693 8 8 47 47 +694 8 47 +695 8 8 10 18 +696 8 11 19 29 35 46 48 49 69 +697 8 11 49 +698 8 47 +699 8 8 10 10 36 50 76 +700 8 30 +701 8 +702 8 10 13 14 21 39 49 51 62 +703 8 48 49 +704 8 22 23 +705 8 +706 9 +707 9 33 73 +708 9 +709 -1 9 10 11 18 31 33 35 48 49 49 50 67 73 +710 9 48 49 +711 9 65 +712 9 +713 9 9 43 +714 9 49 +715 9 +716 9 11 12 48 50 50 +717 9 10 10 12 13 14 48 49 50 50 51 +718 9 19 33 35 47 49 50 66 +719 9 +720 9 11 24 48 69 71 78 78 +721 -1 9 12 12 15 15 15 15 15 15 16 16 16 16 16 21 22 23 40 42 43 47 51 54 55 55 55 55 56 56 56 56 56 56 56 57 57 57 60 63 76 +722 9 16 16 45 47 49 56 +723 9 32 48 71 +724 9 +725 9 10 26 34 47 49 59 65 67 67 67 68 +726 -1 9 10 18 21 24 24 26 26 26 27 28 28 28 28 29 29 30 30 31 31 32 32 33 34 34 35 36 48 49 50 60 65 66 67 67 67 69 69 70 72 72 73 73 74 +727 9 21 48 49 +728 9 12 21 26 47 48 65 66 +729 9 49 +730 9 49 +731 9 10 48 49 +732 9 10 49 +733 9 43 48 +734 9 10 48 49 +735 9 10 49 50 +736 9 48 49 +737 9 10 +738 9 +739 9 +740 9 +741 -1 9 10 11 45 45 45 48 48 49 49 50 54 58 64 +742 9 48 +743 9 10 48 49 +744 9 12 12 12 39 40 42 46 46 71 +745 9 46 47 +746 9 10 11 12 33 48 48 49 49 50 +747 9 49 +748 9 24 32 48 64 71 +749 9 +750 9 49 +751 9 +752 9 49 49 +753 9 60 +754 9 +755 9 46 47 48 48 +756 10 +757 10 50 +758 10 50 +759 10 11 12 20 37 40 50 66 +760 10 10 13 14 42 42 49 51 52 80 82 +761 10 +762 10 12 50 52 +763 10 11 11 30 49 50 51 66 69 74 +764 10 34 50 +765 10 75 +766 -1 10 11 11 11 12 13 15 15 20 22 23 26 39 39 39 39 41 42 43 50 50 50 51 55 57 57 57 58 59 76 77 79 80 81 +767 10 +768 10 61 +769 10 12 12 12 13 13 24 41 41 51 60 79 +770 10 49 +771 10 +772 10 47 49 +773 10 50 +774 10 49 +775 10 11 11 20 22 49 50 50 +776 10 49 +777 -1 10 11 28 34 36 36 37 39 41 49 50 52 53 65 68 72 +778 10 +779 10 +780 10 12 26 31 49 77 +781 10 24 29 49 50 50 70 +782 10 11 19 46 60 +783 10 20 24 33 52 64 69 +784 10 +785 -1 10 11 11 11 11 12 13 18 18 22 24 24 25 25 25 28 32 33 34 35 37 37 37 37 40 47 48 51 51 51 52 52 54 54 55 55 60 60 66 69 70 72 77 77 +786 10 +787 10 49 51 +788 10 +789 10 58 70 +790 -1 10 10 12 13 14 40 47 47 49 53 53 53 72 +791 -1 10 11 11 11 11 11 15 22 25 26 28 29 31 34 35 41 46 46 46 47 50 50 51 53 53 66 66 69 72 72 75 76 79 79 +792 10 11 +793 -1 10 12 13 24 24 25 26 27 29 29 30 31 31 32 32 33 33 37 39 47 52 66 68 70 70 70 71 72 73 73 74 74 75 77 80 +794 10 +795 10 11 25 25 26 26 49 65 +796 10 +797 10 11 11 13 20 50 53 66 77 +798 10 25 +799 10 +800 10 +801 10 +802 10 +803 10 +804 10 50 +805 10 +806 10 +807 10 11 49 50 +808 -1 10 11 11 12 12 12 14 25 25 33 35 43 50 50 51 51 52 52 74 +809 -1 10 11 11 13 41 47 48 48 49 49 50 51 64 +810 10 13 18 28 31 50 51 72 +811 10 49 +812 10 50 +813 10 +814 10 +815 10 +816 10 20 51 +817 10 49 +818 10 +819 10 49 +820 10 10 +821 10 49 +822 10 65 +823 11 25 +824 11 50 +825 11 21 58 65 +826 11 43 47 51 +827 11 +828 11 48 50 51 +829 11 13 51 59 +830 11 50 51 +831 11 21 51 68 +832 11 14 53 +833 11 28 34 51 67 67 +834 11 +835 11 12 +836 11 51 +837 11 +838 11 +839 11 +840 11 13 24 38 46 57 +841 11 34 +842 11 51 53 55 +843 11 32 51 68 72 +844 11 50 +845 11 46 +846 11 +847 11 +848 11 38 38 51 79 +849 11 12 20 21 42 47 51 +850 11 66 +851 11 16 20 47 50 77 +852 11 50 +853 11 +854 11 12 27 29 45 49 50 52 52 68 76 +855 11 +856 11 11 +857 11 23 38 50 50 51 58 63 +858 11 50 50 +859 11 +860 11 46 51 +861 11 48 50 76 +862 11 26 26 52 +863 11 18 49 51 67 73 +864 11 +865 11 28 37 49 50 67 73 76 77 +866 11 50 51 51 +867 11 11 +868 11 +869 11 11 50 69 +870 11 +871 11 50 +872 11 74 75 +873 11 +874 11 50 +875 11 +876 11 +877 11 +878 11 51 +879 11 49 +880 -1 11 12 13 15 15 15 15 15 15 16 16 16 16 22 39 39 40 41 42 43 49 50 50 51 52 54 55 56 56 57 57 57 77 78 79 +881 11 +882 11 +883 11 +884 11 12 24 39 59 +885 11 +886 11 12 51 +887 11 39 42 59 +888 12 12 20 28 35 35 80 +889 12 +890 12 +891 12 34 +892 12 13 +893 12 +894 12 +895 12 76 +896 -1 12 13 13 14 14 14 14 21 21 22 29 36 40 41 42 42 42 42 42 43 50 51 52 52 53 54 54 59 60 60 61 61 75 77 77 82 +897 12 +898 12 13 13 40 51 52 +899 12 +900 12 +901 12 42 66 +902 12 20 21 32 37 41 52 52 61 68 81 81 +903 12 13 30 30 52 69 +904 12 +905 12 +906 -1 12 13 22 36 47 49 51 52 52 59 62 76 77 79 +907 12 12 50 +908 -1 12 12 12 13 15 18 18 20 35 36 51 51 51 52 52 53 58 59 60 60 77 78 78 78 +909 12 12 12 48 51 +910 12 52 +911 12 13 13 29 50 51 51 52 52 59 60 +912 12 52 59 +913 12 28 48 50 51 52 66 +914 12 +915 12 +916 12 13 13 50 51 51 52 53 +917 12 +918 12 +919 -1 12 13 14 18 20 20 43 43 43 47 52 54 59 61 61 61 62 75 77 +920 12 +921 12 +922 12 12 13 18 25 45 52 59 62 65 65 79 +923 12 24 52 +924 12 49 51 74 75 +925 12 +926 12 15 16 20 20 21 46 47 60 61 +927 12 +928 12 +929 12 37 75 +930 12 62 +931 12 26 50 50 52 +932 12 +933 -1 12 32 39 39 43 51 51 51 51 51 51 51 52 52 57 57 +934 12 52 +935 12 51 +936 12 13 18 52 +937 12 +938 12 +939 12 +940 -1 12 13 25 25 29 30 30 32 49 52 65 71 76 78 +941 12 34 36 +942 12 51 52 +943 12 +944 12 12 13 52 53 +945 12 +946 12 52 +947 12 +948 12 +949 12 36 52 +950 12 +951 12 +952 13 35 37 +953 13 35 +954 -1 13 14 15 20 25 27 28 43 45 47 49 50 71 74 75 78 +955 13 53 +956 13 53 58 +957 13 41 +958 -1 13 15 22 38 38 47 50 51 51 55 55 57 62 79 +959 13 +960 13 25 26 27 36 40 51 53 77 82 +961 13 +962 13 69 78 +963 13 +964 13 +965 13 69 +966 13 +967 13 +968 13 43 +969 13 13 +970 13 35 +971 13 +972 13 +973 13 53 53 76 +974 -1 13 22 38 46 47 50 53 53 55 55 55 58 61 67 +975 13 13 +976 13 39 +977 13 +978 13 29 33 34 68 69 71 73 75 79 +979 13 13 31 70 71 71 +980 13 +981 -1 13 13 13 14 14 14 19 20 21 35 35 35 53 53 54 55 59 60 61 +982 13 +983 13 +984 13 19 21 22 32 36 38 42 51 52 59 +985 13 +986 13 24 37 60 +987 13 43 53 53 +988 13 +989 13 +990 13 +991 13 +992 13 52 53 +993 13 +994 13 +995 13 14 52 53 +996 13 14 14 37 52 53 58 +997 13 +998 13 47 +999 13 53 +1000 13 +1001 13 +1002 -1 13 13 24 25 25 25 26 52 53 54 64 65 68 71 77 +1003 13 +1004 13 31 33 51 52 52 +1005 13 +1006 13 53 +1007 13 24 29 59 +1008 13 +1009 13 +1010 13 30 31 53 64 71 +1011 13 13 35 51 51 52 53 +1012 13 +1013 13 +1014 13 14 52 53 +1015 14 +1016 14 +1017 14 +1018 14 32 54 55 60 +1019 14 15 18 19 20 20 38 54 61 82 +1020 14 68 +1021 14 +1022 14 20 34 38 46 46 73 +1023 -1 14 15 22 22 31 55 55 56 58 64 71 80 82 +1024 14 +1025 14 +1026 -1 14 20 21 25 27 28 35 42 43 61 63 67 67 +1027 14 15 26 53 54 72 +1028 14 53 +1029 14 46 53 73 78 +1030 14 53 54 +1031 14 +1032 14 +1033 14 15 53 +1034 14 18 53 53 54 +1035 15 15 15 55 55 55 +1036 15 23 30 43 58 58 +1037 15 55 56 56 +1038 15 +1039 15 +1040 15 54 56 +1041 15 +1042 15 15 22 +1043 15 +1044 15 +1045 15 21 22 22 55 60 61 61 +1046 15 +1047 15 +1048 15 35 +1049 15 16 +1050 15 +1051 15 +1052 15 55 +1053 15 55 +1054 15 55 73 +1055 15 +1056 15 19 19 20 21 23 53 60 61 61 80 +1057 15 21 80 +1058 -1 15 16 16 21 22 22 51 52 52 53 53 53 54 54 55 56 56 56 59 +1059 15 23 41 41 55 55 56 63 79 +1060 -1 15 36 38 41 42 43 43 47 48 48 49 51 55 56 56 57 70 78 +1061 15 +1062 15 55 +1063 15 +1064 15 +1065 15 +1066 15 55 55 +1067 15 55 55 +1068 15 16 54 56 +1069 15 +1070 15 +1071 -1 15 21 22 29 38 45 46 51 52 53 53 54 55 59 +1072 15 +1073 15 55 +1074 -1 15 16 16 16 16 16 16 16 16 16 16 16 16 21 21 26 37 37 40 40 43 47 47 47 47 52 52 53 54 54 55 55 55 55 55 55 55 55 55 55 55 55 55 55 56 56 56 56 56 56 56 57 57 57 57 57 57 57 57 57 57 57 66 67 74 76 78 82 +1075 15 +1076 15 +1077 16 +1078 16 +1079 16 +1080 16 56 +1081 16 +1082 16 18 20 56 57 +1083 16 +1084 16 +1085 16 16 54 55 56 57 +1086 16 56 +1087 16 +1088 16 +1089 16 19 28 55 56 +1090 16 +1091 16 +1092 16 +1093 16 57 +1094 16 +1095 16 +1096 16 +1097 -1 16 26 28 28 37 42 43 45 46 49 49 49 49 50 53 56 56 56 57 58 59 60 62 62 63 77 77 78 +1098 16 56 +1099 16 +1100 16 +1101 16 +1102 16 26 67 80 +1103 16 +1104 18 21 +1105 18 20 59 +1106 18 18 +1107 18 19 59 +1108 18 +1109 18 +1110 18 20 21 22 59 60 61 62 +1111 18 +1112 18 59 +1113 18 +1114 18 +1115 18 +1116 18 +1117 18 19 +1118 18 43 +1119 18 18 +1120 18 +1121 18 +1122 18 22 +1123 18 18 35 41 81 +1124 18 25 25 34 35 41 66 66 73 76 +1125 18 +1126 18 21 +1127 18 +1128 18 +1129 18 +1130 18 +1131 18 19 +1132 18 +1133 18 19 +1134 18 58 +1135 18 18 +1136 18 20 21 22 23 62 63 +1137 18 60 74 75 +1138 18 59 +1139 18 58 +1140 18 20 21 21 23 59 60 62 +1141 18 +1142 18 19 19 20 20 58 59 60 60 60 61 61 +1143 18 21 22 35 58 +1144 18 +1145 18 19 24 59 60 +1146 -1 18 18 19 19 25 27 28 29 31 32 33 33 34 36 36 37 45 46 51 51 52 59 59 59 60 60 60 60 65 66 68 68 72 74 75 76 79 79 +1147 18 +1148 18 +1149 18 +1150 18 +1151 18 20 21 43 60 +1152 -1 18 18 20 20 21 21 21 22 23 23 38 41 60 60 60 60 62 62 63 +1153 18 19 25 25 64 65 70 80 +1154 18 +1155 18 +1156 18 58 +1157 18 +1158 18 19 19 59 61 61 +1159 18 38 +1160 18 19 +1161 18 +1162 18 28 33 +1163 18 +1164 18 +1165 18 +1166 -1 18 19 19 20 20 20 20 49 51 53 53 55 58 59 60 60 60 61 64 76 77 +1167 18 +1168 18 59 60 +1169 18 18 +1170 18 19 59 59 +1171 18 24 25 32 35 59 +1172 18 18 19 20 59 +1173 18 19 20 20 22 22 23 31 59 62 62 79 +1174 18 21 54 62 +1175 18 +1176 18 +1177 18 26 66 +1178 18 58 58 +1179 18 +1180 18 60 +1181 18 19 58 59 59 59 +1182 18 19 59 59 59 60 +1183 18 +1184 18 19 59 59 +1185 18 59 +1186 18 59 +1187 18 +1188 18 20 60 +1189 18 51 66 +1190 18 +1191 18 +1192 18 41 43 +1193 18 18 23 +1194 18 19 20 24 52 53 59 59 77 79 +1195 18 +1196 18 +1197 18 20 21 34 60 61 82 +1198 18 +1199 18 +1200 18 +1201 18 +1202 18 +1203 18 +1204 18 61 +1205 18 +1206 18 47 +1207 18 24 26 77 82 +1208 18 60 +1209 18 78 +1210 18 +1211 18 +1212 18 19 20 +1213 18 58 +1214 18 +1215 -1 18 20 21 22 29 31 50 60 67 67 72 78 79 79 79 79 +1216 18 +1217 -1 18 18 26 28 28 36 37 46 51 56 58 64 66 68 68 76 76 76 +1218 18 +1219 18 25 25 51 53 59 +1220 18 +1221 18 41 +1222 18 +1223 18 +1224 18 +1225 18 30 60 +1226 18 58 +1227 19 +1228 19 33 43 54 56 59 +1229 -1 19 20 24 28 28 32 32 32 34 63 64 65 65 66 67 73 73 74 +1230 19 22 23 23 61 +1231 19 +1232 19 19 +1233 19 +1234 19 58 +1235 19 +1236 19 +1237 19 +1238 19 21 28 30 42 69 70 +1239 19 +1240 19 +1241 19 +1242 19 +1243 19 +1244 19 28 43 81 +1245 19 59 +1246 19 27 33 35 58 +1247 19 +1248 19 +1249 19 26 35 36 74 +1250 19 +1251 19 +1252 19 +1253 19 +1254 19 +1255 19 +1256 19 20 59 60 60 +1257 19 60 +1258 19 +1259 19 +1260 19 +1261 19 +1262 19 59 +1263 19 59 +1264 19 59 +1265 19 59 +1266 19 34 +1267 19 23 77 +1268 19 +1269 19 47 59 +1270 19 +1271 19 58 +1272 19 +1273 19 +1274 19 24 58 59 66 73 +1275 19 +1276 19 +1277 19 +1278 19 19 59 +1279 19 +1280 20 20 24 32 76 +1281 20 +1282 20 30 79 +1283 20 +1284 20 60 +1285 20 60 +1286 20 37 60 +1287 20 +1288 20 +1289 20 60 +1290 20 21 23 60 +1291 20 21 22 +1292 20 22 23 23 59 62 62 80 82 +1293 20 60 +1294 20 21 60 +1295 20 +1296 20 22 +1297 20 60 +1298 20 60 +1299 20 20 +1300 20 +1301 20 +1302 20 29 35 37 53 60 60 +1303 20 60 +1304 20 +1305 20 60 +1306 20 60 +1307 20 21 +1308 20 60 +1309 20 52 60 +1310 20 51 +1311 20 60 +1312 20 60 +1313 20 26 32 34 52 +1314 20 +1315 20 21 21 60 61 78 +1316 20 22 60 62 +1317 20 60 +1318 20 +1319 20 61 +1320 20 29 60 +1321 20 60 +1322 20 24 24 28 65 +1323 20 60 +1324 20 59 60 60 +1325 20 60 +1326 20 60 +1327 20 60 +1328 20 60 +1329 20 60 +1330 20 60 +1331 20 22 23 +1332 20 +1333 20 +1334 -1 20 22 22 25 25 26 26 26 26 27 28 28 29 31 35 35 36 37 37 37 39 39 39 40 40 40 41 41 42 42 43 45 47 47 50 50 51 52 53 55 56 56 57 57 58 59 59 60 60 63 65 65 66 66 66 70 72 73 74 75 75 75 75 77 77 77 77 78 78 81 +1335 20 21 21 58 59 59 60 61 61 +1336 20 +1337 20 +1338 20 60 +1339 20 60 +1340 20 +1341 -1 20 27 27 29 36 60 65 65 69 75 75 77 77 +1342 20 21 59 61 +1343 20 21 22 22 23 60 61 62 +1344 20 +1345 20 +1346 20 +1347 20 54 55 61 +1348 20 +1349 20 60 76 +1350 20 51 60 60 +1351 20 60 +1352 20 20 +1353 20 +1354 20 +1355 20 23 +1356 20 +1357 20 +1358 20 60 +1359 20 60 +1360 20 +1361 20 +1362 20 +1363 20 21 59 60 +1364 20 29 +1365 20 +1366 20 +1367 21 68 +1368 21 61 +1369 21 +1370 21 +1371 21 +1372 21 46 61 +1373 21 80 +1374 21 39 +1375 21 24 38 38 39 47 61 66 +1376 21 21 61 +1377 21 +1378 21 +1379 21 +1380 21 61 +1381 21 61 +1382 21 +1383 21 +1384 21 25 47 67 +1385 21 +1386 21 +1387 -1 21 27 28 28 29 33 34 37 43 45 48 53 67 67 68 75 81 +1388 21 22 +1389 21 48 50 55 55 +1390 21 22 61 62 +1391 21 +1392 21 61 +1393 21 +1394 21 22 25 43 61 +1395 21 61 +1396 21 22 23 62 63 +1397 21 +1398 21 61 +1399 21 +1400 21 22 62 +1401 21 46 56 60 61 70 82 +1402 21 61 +1403 21 82 +1404 21 +1405 21 +1406 21 +1407 21 +1408 21 +1409 21 61 +1410 21 +1411 21 +1412 21 +1413 21 61 +1414 21 +1415 21 +1416 21 +1417 21 +1418 21 +1419 21 22 62 +1420 21 61 +1421 21 61 +1422 21 61 +1423 21 +1424 21 22 59 61 62 +1425 21 47 51 71 +1426 21 22 59 61 63 +1427 21 24 +1428 21 +1429 21 +1430 21 +1431 21 60 +1432 21 28 39 60 61 65 68 68 +1433 22 62 +1434 22 +1435 22 +1436 22 62 +1437 22 62 62 +1438 22 62 +1439 22 +1440 22 62 +1441 22 +1442 22 62 +1443 22 22 +1444 22 +1445 22 +1446 22 35 41 +1447 22 35 73 74 +1448 22 22 22 24 62 +1449 22 +1450 22 81 +1451 22 +1452 22 22 62 +1453 22 24 62 +1454 22 62 +1455 22 +1456 22 +1457 22 +1458 22 62 +1459 22 +1460 22 +1461 22 22 +1462 22 41 50 51 55 78 +1463 22 62 +1464 22 26 +1465 22 26 27 29 34 43 45 45 62 68 +1466 22 34 +1467 22 29 31 62 62 +1468 22 +1469 22 +1470 22 +1471 22 22 50 50 52 +1472 22 +1473 22 +1474 22 +1475 22 +1476 22 23 +1477 22 25 28 62 62 +1478 -1 22 25 31 32 32 33 34 37 40 43 51 56 59 60 62 66 67 70 70 72 73 75 75 76 76 80 80 80 80 +1479 22 45 50 53 56 56 58 62 79 79 +1480 22 +1481 22 +1482 22 +1483 22 +1484 22 +1485 22 52 62 +1486 22 59 60 61 +1487 22 61 +1488 22 +1489 22 62 63 +1490 23 25 30 32 34 34 55 70 +1491 23 +1492 23 23 24 47 56 57 +1493 23 62 +1494 23 +1495 23 63 +1496 23 +1497 23 63 +1498 23 +1499 23 +1500 23 62 63 +1501 23 46 63 +1502 23 +1503 23 +1504 23 +1505 23 +1506 23 59 59 60 62 80 +1507 23 61 +1508 23 61 +1509 24 25 +1510 -1 24 25 29 32 32 37 38 40 40 58 64 69 70 73 76 80 +1511 24 25 27 64 +1512 24 +1513 24 +1514 24 +1515 24 26 27 39 51 77 80 +1516 24 24 37 76 +1517 24 31 32 32 64 65 75 +1518 24 29 67 +1519 24 52 59 64 +1520 24 25 26 32 64 +1521 24 29 32 47 61 +1522 24 +1523 24 +1524 24 26 +1525 24 64 +1526 24 +1527 24 29 34 69 80 +1528 24 31 +1529 24 25 +1530 24 +1531 24 31 65 +1532 24 +1533 24 +1534 24 +1535 24 25 26 27 28 29 64 65 65 66 68 70 +1536 24 +1537 24 +1538 24 +1539 -1 24 32 48 49 49 53 64 64 71 72 73 73 75 +1540 24 25 27 29 30 30 67 71 +1541 24 +1542 24 54 64 64 +1543 24 38 69 +1544 24 24 25 64 64 65 +1545 24 33 38 38 38 47 73 +1546 24 25 29 31 33 34 64 69 +1547 24 64 +1548 24 64 +1549 -1 24 25 25 26 27 28 31 34 45 64 66 68 71 +1550 24 +1551 24 25 64 +1552 24 24 64 +1553 24 +1554 24 64 +1555 24 25 64 +1556 24 +1557 24 66 +1558 24 +1559 24 25 25 26 28 64 65 +1560 24 26 64 +1561 24 24 25 26 65 +1562 24 25 25 25 64 64 64 65 +1563 24 24 +1564 24 +1565 24 +1566 -1 24 24 24 25 25 25 25 25 25 26 26 26 26 26 27 27 28 31 64 64 65 65 65 65 66 66 66 68 +1567 24 +1568 24 +1569 24 26 +1570 24 25 +1571 24 25 65 +1572 24 25 28 31 64 65 +1573 24 25 +1574 24 25 25 28 59 64 +1575 24 32 61 +1576 24 64 +1577 24 65 +1578 24 +1579 24 64 70 +1580 24 25 64 64 65 +1581 24 +1582 24 28 74 76 +1583 24 28 68 +1584 24 25 26 +1585 24 25 +1586 24 64 +1587 24 82 +1588 24 25 64 +1589 24 25 53 64 +1590 24 38 +1591 24 34 59 60 64 +1592 24 +1593 24 64 +1594 24 26 64 +1595 24 27 27 64 64 66 +1596 24 +1597 24 30 33 64 +1598 24 64 +1599 24 +1600 24 59 +1601 -1 24 25 25 25 25 26 28 35 64 65 72 77 80 +1602 24 +1603 24 64 +1604 24 25 35 51 58 64 65 77 +1605 24 24 64 67 +1606 24 +1607 24 64 +1608 24 64 74 +1609 24 +1610 24 25 64 +1611 24 +1612 24 30 +1613 24 +1614 24 65 +1615 24 +1616 24 64 +1617 24 64 +1618 25 37 40 76 +1619 25 40 40 53 55 65 73 74 79 +1620 25 +1621 25 65 67 +1622 25 +1623 25 65 +1624 -1 25 26 26 27 29 33 34 35 43 45 47 49 50 53 58 67 67 68 76 77 +1625 25 +1626 25 +1627 25 27 +1628 25 +1629 25 26 65 66 +1630 25 27 +1631 25 +1632 25 26 27 +1633 25 26 27 64 67 68 69 69 +1634 25 65 +1635 25 28 +1636 25 60 61 80 +1637 25 65 +1638 25 64 +1639 25 65 66 +1640 25 26 27 31 32 35 65 75 +1641 25 +1642 25 +1643 25 40 64 65 +1644 25 +1645 25 29 31 +1646 25 +1647 25 +1648 25 +1649 -1 25 26 26 28 40 43 52 59 64 65 65 66 66 68 69 74 +1650 25 65 69 +1651 25 +1652 25 64 +1653 25 +1654 25 29 29 66 67 67 68 +1655 25 +1656 25 +1657 25 28 +1658 25 26 28 64 65 66 67 +1659 25 +1660 25 +1661 25 26 26 27 34 49 49 49 66 67 71 71 +1662 25 26 48 65 66 +1663 25 25 65 +1664 25 +1665 25 42 71 +1666 25 +1667 25 +1668 25 50 +1669 25 28 64 65 68 +1670 25 +1671 25 69 +1672 25 26 +1673 25 69 +1674 25 +1675 25 27 27 32 66 70 71 +1676 25 30 36 70 80 +1677 25 65 +1678 25 77 +1679 25 +1680 25 65 65 +1681 25 26 66 +1682 25 27 67 72 +1683 25 65 +1684 25 43 65 +1685 25 64 +1686 25 65 +1687 25 65 77 +1688 25 27 34 65 77 +1689 25 +1690 25 65 +1691 25 65 +1692 25 25 +1693 -1 25 26 26 26 27 27 31 31 31 31 32 33 33 49 53 65 65 66 66 67 75 +1694 25 +1695 25 64 +1696 25 65 71 +1697 25 +1698 25 65 66 +1699 25 29 +1700 25 64 65 77 +1701 26 28 28 29 64 +1702 26 26 65 66 +1703 26 27 32 72 +1704 26 26 26 58 +1705 26 +1706 26 +1707 26 65 +1708 26 65 +1709 26 53 +1710 26 +1711 26 66 +1712 26 +1713 26 77 +1714 26 33 66 +1715 26 28 32 52 71 72 72 +1716 26 27 28 66 67 68 +1717 26 +1718 26 +1719 26 +1720 26 +1721 26 +1722 26 +1723 26 +1724 26 +1725 26 +1726 26 +1727 26 39 66 66 66 +1728 26 64 66 +1729 26 28 67 +1730 26 28 34 66 70 72 +1731 26 +1732 26 33 57 66 66 72 +1733 26 26 +1734 26 31 33 +1735 26 27 33 64 65 68 +1736 26 66 +1737 26 +1738 26 26 +1739 26 +1740 26 28 66 73 +1741 26 +1742 26 66 +1743 26 +1744 26 28 66 +1745 26 +1746 26 +1747 26 +1748 26 26 +1749 26 72 +1750 26 +1751 26 +1752 26 70 +1753 26 +1754 26 +1755 26 27 29 67 68 +1756 26 +1757 26 65 66 +1758 26 +1759 26 27 28 64 64 65 66 66 67 68 +1760 26 29 65 66 +1761 26 +1762 26 60 66 66 66 78 +1763 26 +1764 26 +1765 26 27 47 66 69 +1766 26 +1767 26 +1768 26 65 66 +1769 26 27 66 +1770 26 32 59 65 71 +1771 26 29 37 43 65 +1772 26 71 +1773 26 28 +1774 26 30 39 58 +1775 26 27 +1776 26 +1777 26 +1778 26 +1779 26 +1780 26 64 66 +1781 27 +1782 27 +1783 27 32 33 37 38 41 60 61 68 72 73 +1784 27 +1785 27 +1786 27 +1787 27 +1788 27 29 29 46 46 69 +1789 27 +1790 27 +1791 27 +1792 27 +1793 27 +1794 27 27 29 30 +1795 27 +1796 27 +1797 27 +1798 27 28 67 +1799 27 28 +1800 27 +1801 27 +1802 27 +1803 27 37 +1804 27 +1805 27 +1806 27 +1807 27 +1808 27 +1809 27 +1810 27 +1811 27 +1812 27 +1813 27 28 +1814 -1 27 31 36 41 48 49 49 50 50 53 66 70 79 +1815 27 28 28 +1816 27 66 +1817 27 64 +1818 27 +1819 27 29 +1820 27 +1821 27 +1822 27 +1823 27 +1824 27 +1825 27 +1826 27 +1827 27 66 +1828 27 28 +1829 27 +1830 27 +1831 27 +1832 27 51 +1833 27 +1834 27 +1835 27 +1836 27 34 +1837 27 66 +1838 27 67 +1839 27 +1840 27 66 66 +1841 27 66 +1842 28 +1843 28 +1844 28 36 53 67 77 80 +1845 28 +1846 28 68 +1847 28 51 67 +1848 28 +1849 28 +1850 28 29 67 +1851 28 29 67 68 +1852 28 31 36 65 67 +1853 28 +1854 28 +1855 28 +1856 28 31 32 +1857 28 29 30 67 68 69 +1858 28 +1859 28 29 30 31 32 67 68 69 70 71 +1860 28 72 +1861 28 29 68 +1862 28 +1863 28 67 +1864 28 +1865 28 29 30 30 30 68 69 +1866 28 +1867 28 29 +1868 28 77 +1869 28 +1870 28 29 31 69 70 +1871 28 +1872 28 68 68 +1873 28 +1874 28 +1875 28 +1876 28 +1877 28 +1878 28 +1879 28 +1880 28 70 +1881 28 68 68 +1882 28 67 +1883 28 +1884 28 43 +1885 28 +1886 28 +1887 28 +1888 28 +1889 28 +1890 28 +1891 28 29 33 34 67 72 73 +1892 28 +1893 28 28 67 +1894 28 +1895 28 +1896 28 +1897 28 +1898 28 65 68 +1899 28 +1900 28 30 69 +1901 28 34 66 68 73 +1902 28 67 +1903 28 67 +1904 28 65 66 70 +1905 28 +1906 28 +1907 28 29 64 66 68 +1908 28 30 65 68 +1909 28 +1910 28 +1911 28 +1912 28 34 66 69 72 75 +1913 28 +1914 28 33 67 70 73 79 +1915 28 +1916 28 +1917 28 +1918 28 +1919 28 65 +1920 28 +1921 28 66 +1922 28 67 68 +1923 28 +1924 28 +1925 28 68 +1926 28 +1927 28 67 +1928 28 67 +1929 28 +1930 28 +1931 28 +1932 28 68 +1933 28 +1934 28 60 +1935 28 47 +1936 28 43 46 46 63 +1937 28 67 +1938 28 67 +1939 28 35 35 37 37 51 52 59 65 68 73 73 +1940 28 30 +1941 28 +1942 28 69 +1943 28 +1944 28 +1945 28 +1946 28 68 +1947 28 +1948 28 29 66 67 68 69 +1949 28 +1950 28 +1951 28 +1952 29 31 35 50 65 66 66 69 74 77 +1953 29 30 31 31 32 50 67 68 69 +1954 29 31 36 68 69 74 75 +1955 29 +1956 29 +1957 29 30 32 68 +1958 29 30 31 35 39 49 58 67 67 70 74 79 +1959 29 30 68 70 +1960 29 30 +1961 29 +1962 29 68 68 +1963 29 60 68 +1964 29 68 74 +1965 29 68 74 +1966 29 +1967 29 34 35 74 +1968 29 30 68 72 72 73 +1969 29 +1970 29 30 +1971 29 +1972 29 30 31 31 32 34 68 72 +1973 29 +1974 29 68 +1975 29 +1976 29 29 32 +1977 29 +1978 29 +1979 29 +1980 29 +1981 29 +1982 29 +1983 29 +1984 29 +1985 29 +1986 29 +1987 29 68 +1988 29 68 +1989 29 66 +1990 29 64 68 69 +1991 29 68 +1992 29 33 34 36 49 50 68 75 81 +1993 29 +1994 29 30 30 +1995 29 68 +1996 29 +1997 29 64 68 +1998 29 34 +1999 29 +2000 29 +2001 29 +2002 29 +2003 29 +2004 29 +2005 29 30 +2006 29 64 70 +2007 29 69 +2008 29 68 +2009 29 68 +2010 29 46 68 +2011 29 68 69 +2012 29 30 69 +2013 29 29 68 +2014 29 +2015 29 +2016 29 +2017 29 29 +2018 29 73 +2019 29 69 +2020 29 +2021 29 +2022 29 +2023 29 +2024 29 +2025 29 30 31 68 70 71 +2026 29 +2027 29 41 51 57 79 +2028 29 36 68 69 69 76 +2029 29 +2030 29 +2031 29 +2032 29 68 +2033 29 +2034 29 68 +2035 29 +2036 29 +2037 29 70 +2038 29 +2039 29 +2040 29 64 66 73 +2041 29 57 +2042 29 +2043 29 42 59 +2044 29 32 42 70 72 +2045 29 30 54 65 +2046 -1 29 31 32 33 37 40 43 50 67 72 73 74 74 76 81 +2047 29 +2048 29 +2049 29 +2050 29 31 71 +2051 29 29 +2052 29 +2053 29 +2054 29 +2055 29 68 +2056 29 +2057 29 +2058 29 37 43 53 75 +2059 29 36 +2060 29 +2061 29 68 69 +2062 29 68 +2063 29 +2064 30 69 +2065 30 31 32 64 66 +2066 30 31 +2067 30 31 70 +2068 30 30 31 +2069 30 +2070 30 31 35 60 69 70 +2071 30 69 +2072 30 31 32 32 33 33 36 50 58 65 68 72 +2073 30 +2074 30 31 69 +2075 30 +2076 30 +2077 30 69 +2078 30 +2079 30 +2080 30 +2081 30 47 48 48 49 69 70 +2082 30 69 +2083 30 30 66 69 +2084 30 30 +2085 30 31 53 +2086 30 +2087 30 +2088 30 67 +2089 30 32 35 +2090 30 +2091 30 +2092 30 +2093 30 +2094 30 +2095 30 +2096 30 +2097 30 31 32 65 70 71 +2098 30 30 31 67 70 +2099 30 +2100 30 +2101 30 72 73 74 +2102 30 32 +2103 30 +2104 30 +2105 30 +2106 30 +2107 30 30 32 +2108 30 +2109 30 33 64 68 69 70 71 +2110 30 30 31 +2111 30 31 70 +2112 30 +2113 30 +2114 30 69 +2115 30 36 50 +2116 30 +2117 30 +2118 30 +2119 30 +2120 30 +2121 30 +2122 30 +2123 30 +2124 30 30 +2125 30 32 +2126 30 +2127 30 30 +2128 30 +2129 30 +2130 30 +2131 30 +2132 30 +2133 30 51 +2134 30 33 72 +2135 30 +2136 30 69 +2137 30 +2138 30 +2139 30 +2140 30 +2141 -1 30 32 32 33 35 35 39 45 47 47 48 49 49 50 65 68 71 72 72 75 77 78 79 79 80 +2142 30 31 70 +2143 30 +2144 30 31 +2145 30 69 +2146 30 69 +2147 30 +2148 30 +2149 30 31 71 +2150 30 31 68 +2151 30 59 82 +2152 30 33 +2153 30 +2154 30 69 +2155 30 +2156 30 +2157 30 +2158 30 +2159 30 +2160 30 +2161 30 31 33 33 69 +2162 30 +2163 30 +2164 30 30 +2165 30 34 +2166 30 66 +2167 30 72 +2168 30 +2169 30 +2170 30 +2171 30 69 72 +2172 30 67 69 +2173 30 +2174 30 +2175 30 +2176 30 +2177 30 +2178 30 +2179 30 +2180 31 +2181 31 40 70 +2182 31 40 65 77 +2183 31 70 +2184 31 +2185 31 +2186 31 +2187 31 +2188 31 +2189 31 +2190 31 +2191 31 +2192 31 +2193 31 +2194 31 71 +2195 31 70 +2196 31 32 71 72 +2197 31 +2198 31 32 64 72 +2199 31 +2200 31 +2201 31 +2202 31 +2203 31 +2204 31 +2205 31 +2206 31 +2207 31 +2208 31 32 32 40 45 59 60 65 66 70 72 +2209 31 38 68 81 +2210 31 +2211 31 +2212 31 +2213 31 +2214 31 +2215 31 +2216 31 +2217 31 +2218 31 40 +2219 31 69 +2220 31 +2221 31 +2222 31 +2223 31 +2224 31 +2225 31 +2226 31 +2227 31 32 33 71 72 +2228 31 43 +2229 31 32 +2230 31 32 32 71 71 72 +2231 31 32 +2232 31 +2233 31 70 +2234 31 +2235 31 33 34 72 75 +2236 31 +2237 31 +2238 31 31 81 +2239 31 +2240 31 +2241 31 70 +2242 31 38 +2243 31 70 +2244 31 +2245 31 70 +2246 31 +2247 31 +2248 31 +2249 31 70 +2250 31 32 66 67 68 68 69 70 72 +2251 31 +2252 31 +2253 31 34 71 72 +2254 31 +2255 31 32 +2256 31 +2257 31 +2258 31 31 36 +2259 31 31 79 +2260 31 +2261 31 70 +2262 31 +2263 31 70 +2264 31 +2265 31 70 +2266 31 70 +2267 31 +2268 31 70 +2269 31 +2270 31 +2271 31 70 +2272 31 39 70 +2273 31 +2274 31 +2275 31 +2276 31 70 +2277 31 +2278 31 +2279 31 +2280 31 +2281 32 32 41 41 73 +2282 32 +2283 32 +2284 32 +2285 32 +2286 32 +2287 32 79 79 80 +2288 32 32 36 67 +2289 32 +2290 32 +2291 32 +2292 32 47 +2293 32 +2294 32 +2295 32 +2296 32 +2297 32 32 +2298 32 +2299 32 72 72 +2300 32 +2301 32 53 +2302 32 +2303 32 33 71 72 +2304 32 +2305 32 +2306 32 +2307 32 33 +2308 32 34 72 +2309 32 33 +2310 32 34 73 +2311 32 41 65 +2312 32 +2313 32 41 +2314 32 +2315 32 41 58 +2316 32 +2317 32 +2318 32 +2319 32 41 +2320 32 +2321 32 +2322 32 72 +2323 32 +2324 32 +2325 32 +2326 32 64 71 +2327 32 72 +2328 32 +2329 32 +2330 32 +2331 32 37 37 67 +2332 32 +2333 32 64 64 70 +2334 32 71 71 +2335 32 +2336 32 33 +2337 32 +2338 32 33 72 78 +2339 32 +2340 32 +2341 32 +2342 32 71 +2343 32 +2344 32 +2345 32 +2346 32 33 +2347 32 +2348 32 +2349 32 71 +2350 32 39 46 55 58 65 67 68 70 71 75 +2351 32 +2352 32 33 34 35 36 67 67 72 73 +2353 32 33 70 +2354 32 +2355 32 +2356 32 +2357 32 80 +2358 32 +2359 32 +2360 32 +2361 32 72 +2362 32 +2363 32 +2364 32 +2365 32 33 70 71 72 75 +2366 32 71 71 +2367 32 35 61 67 71 71 71 77 +2368 32 +2369 32 65 71 72 +2370 32 +2371 32 +2372 32 +2373 32 72 +2374 32 70 +2375 32 +2376 33 +2377 33 +2378 33 67 +2379 33 35 +2380 33 +2381 33 +2382 33 +2383 33 69 75 +2384 33 +2385 33 34 66 77 +2386 33 +2387 33 34 35 36 37 74 75 76 +2388 33 +2389 33 +2390 33 72 +2391 33 72 +2392 33 72 +2393 33 +2394 33 34 73 +2395 33 +2396 33 35 45 45 56 64 69 71 72 74 77 +2397 33 72 74 +2398 33 72 +2399 33 34 62 71 +2400 33 +2401 33 35 72 +2402 33 +2403 33 +2404 33 72 +2405 33 34 +2406 33 +2407 33 +2408 33 72 +2409 33 73 +2410 33 +2411 33 71 72 72 +2412 33 +2413 33 +2414 33 +2415 33 +2416 33 45 +2417 33 +2418 33 +2419 34 40 52 59 60 82 +2420 34 +2421 34 34 43 46 49 54 55 58 60 63 +2422 34 37 77 77 +2423 34 76 +2424 -1 34 35 35 36 37 37 64 72 73 74 76 76 77 77 77 78 +2425 34 +2426 34 39 40 51 61 81 +2427 34 +2428 34 49 +2429 34 41 46 55 65 73 74 74 +2430 34 34 73 74 +2431 34 74 +2432 34 35 36 39 48 64 69 73 74 79 +2433 34 35 41 68 73 76 +2434 34 35 73 74 +2435 34 74 +2436 34 73 +2437 34 +2438 34 35 +2439 34 35 35 74 +2440 34 35 36 +2441 34 +2442 -1 34 42 42 43 43 45 46 46 47 47 58 59 59 75 +2443 34 +2444 34 72 +2445 34 +2446 34 35 +2447 34 +2448 34 35 +2449 34 +2450 34 73 74 +2451 34 34 +2452 34 +2453 34 +2454 34 +2455 34 +2456 34 +2457 34 73 +2458 34 74 +2459 34 67 71 73 +2460 34 +2461 35 +2462 35 +2463 35 +2464 35 36 +2465 35 79 +2466 35 36 37 75 77 78 +2467 35 36 37 75 76 77 +2468 35 36 +2469 35 +2470 35 36 76 77 78 78 +2471 35 75 +2472 35 +2473 35 68 +2474 35 36 37 74 75 +2475 35 +2476 35 +2477 35 39 50 52 +2478 35 +2479 35 41 +2480 35 +2481 35 +2482 35 75 +2483 35 75 +2484 35 41 43 47 47 47 47 52 77 +2485 35 36 76 +2486 35 73 75 +2487 35 36 +2488 35 37 76 +2489 35 49 +2490 35 75 +2491 35 +2492 35 71 73 75 +2493 35 +2494 35 +2495 35 36 37 75 +2496 35 80 +2497 35 77 +2498 35 +2499 35 +2500 35 36 +2501 35 36 +2502 35 50 64 +2503 35 73 74 +2504 35 +2505 35 +2506 35 +2507 35 +2508 35 74 +2509 35 74 +2510 35 +2511 35 +2512 35 +2513 36 +2514 36 +2515 36 +2516 36 +2517 36 37 77 78 +2518 36 +2519 36 37 75 76 +2520 36 +2521 36 38 +2522 36 +2523 36 +2524 36 +2525 36 +2526 36 75 +2527 36 55 63 +2528 36 70 75 75 75 77 +2529 36 36 75 +2530 36 37 37 76 +2531 36 74 76 +2532 36 43 +2533 36 +2534 36 37 37 75 76 76 77 80 +2535 36 37 69 +2536 36 37 37 51 75 76 76 77 78 +2537 36 73 75 +2538 36 +2539 36 37 75 76 +2540 36 +2541 36 75 77 +2542 36 37 76 77 +2543 36 74 +2544 36 39 79 +2545 36 46 50 59 68 72 75 +2546 36 37 75 +2547 36 +2548 36 +2549 36 37 75 76 +2550 36 50 65 71 72 76 +2551 36 +2552 36 75 +2553 36 75 +2554 36 49 58 +2555 36 74 75 +2556 36 75 +2557 36 36 37 48 64 64 67 75 75 76 76 +2558 36 75 +2559 36 +2560 36 67 75 +2561 36 +2562 37 39 43 48 56 59 68 75 76 76 +2563 37 +2564 37 68 72 +2565 37 38 +2566 37 76 77 +2567 37 51 51 77 +2568 37 77 77 +2569 37 76 77 +2570 37 77 +2571 37 41 41 48 56 77 80 82 +2572 37 39 77 77 78 +2573 37 +2574 37 67 76 +2575 37 39 59 80 +2576 37 43 76 +2577 37 37 76 77 78 +2578 37 76 77 78 +2579 37 +2580 37 77 78 +2581 37 76 77 +2582 37 +2583 37 71 +2584 37 +2585 37 +2586 37 75 77 +2587 37 76 +2588 37 76 +2589 37 77 +2590 37 58 74 75 76 76 76 77 77 77 78 +2591 37 +2592 37 41 +2593 37 77 +2594 37 37 40 64 76 76 +2595 37 76 +2596 37 50 +2597 37 75 76 +2598 37 76 +2599 37 76 77 77 +2600 37 76 77 +2601 37 76 +2602 38 +2603 38 +2604 38 +2605 38 +2606 38 +2607 38 +2608 38 63 +2609 38 46 +2610 38 +2611 38 +2612 38 42 48 +2613 38 +2614 38 +2615 38 +2616 38 38 70 +2617 38 +2618 38 74 +2619 38 +2620 38 +2621 38 38 +2622 38 +2623 38 82 +2624 38 +2625 38 +2626 38 +2627 38 +2628 38 +2629 38 +2630 38 +2631 38 78 +2632 38 +2633 38 38 +2634 38 +2635 38 +2636 38 +2637 38 +2638 38 55 +2639 38 +2640 38 +2641 38 +2642 38 +2643 38 +2644 38 +2645 38 +2646 38 +2647 38 +2648 39 +2649 39 +2650 39 79 +2651 39 81 +2652 39 79 +2653 39 42 71 80 +2654 39 76 82 +2655 39 39 79 +2656 39 +2657 39 +2658 39 80 +2659 39 79 +2660 39 +2661 39 79 +2662 39 +2663 39 +2664 39 52 53 61 65 67 70 76 77 77 82 +2665 39 80 +2666 39 +2667 39 +2668 39 79 80 +2669 39 79 80 +2670 39 +2671 39 80 +2672 39 50 79 80 +2673 39 42 47 72 78 79 +2674 -1 39 41 42 59 61 62 64 66 69 81 82 82 82 +2675 39 40 79 80 +2676 39 +2677 39 39 79 80 +2678 39 40 79 80 81 +2679 39 +2680 39 +2681 39 39 79 79 +2682 39 +2683 39 45 70 79 +2684 39 +2685 39 +2686 39 +2687 39 +2688 39 39 80 80 +2689 39 39 40 +2690 39 +2691 39 +2692 39 +2693 39 +2694 39 +2695 39 +2696 39 79 79 80 +2697 39 +2698 39 +2699 39 +2700 39 53 +2701 39 60 +2702 39 +2703 39 80 +2704 39 40 68 79 +2705 39 +2706 39 +2707 39 40 51 80 +2708 39 +2709 39 +2710 39 +2711 39 +2712 39 +2713 39 40 41 79 79 81 81 81 +2714 39 +2715 39 40 41 59 +2716 39 40 80 +2717 39 79 +2718 39 +2719 39 +2720 39 +2721 39 +2722 39 40 +2723 40 +2724 40 +2725 40 +2726 40 60 68 +2727 40 +2728 40 +2729 40 41 +2730 40 +2731 40 41 80 +2732 40 +2733 40 +2734 40 +2735 40 81 +2736 40 +2737 40 +2738 40 70 81 +2739 40 +2740 40 80 +2741 40 77 +2742 40 58 58 59 +2743 40 +2744 40 79 80 +2745 40 74 +2746 40 40 +2747 40 +2748 40 79 81 +2749 40 68 +2750 40 +2751 40 +2752 40 +2753 40 80 +2754 40 +2755 40 +2756 40 +2757 40 81 +2758 40 +2759 40 41 +2760 40 79 80 80 81 81 +2761 40 79 +2762 40 +2763 40 80 81 +2764 40 80 +2765 40 +2766 40 47 52 76 80 +2767 40 80 +2768 40 +2769 40 +2770 40 +2771 40 +2772 40 60 +2773 40 45 +2774 41 60 65 +2775 41 +2776 41 71 80 +2777 41 82 +2778 41 +2779 41 42 +2780 41 80 82 +2781 41 +2782 41 +2783 41 +2784 41 +2785 41 +2786 41 41 50 +2787 41 +2788 41 +2789 41 +2790 41 +2791 41 +2792 41 +2793 41 +2794 41 +2795 41 41 +2796 41 42 50 55 55 56 60 61 79 80 82 +2797 41 +2798 41 +2799 41 +2800 41 47 81 +2801 41 80 81 +2802 41 +2803 41 46 79 +2804 41 50 +2805 41 +2806 41 +2807 41 42 80 81 +2808 41 81 82 +2809 41 +2810 41 +2811 41 +2812 41 48 48 79 +2813 41 +2814 41 +2815 41 43 52 76 +2816 41 71 +2817 41 43 70 72 +2818 41 +2819 41 +2820 41 +2821 41 47 80 +2822 42 42 +2823 42 65 66 66 +2824 42 +2825 42 +2826 42 +2827 42 +2828 42 61 68 69 69 +2829 42 +2830 42 +2831 42 +2832 42 64 +2833 42 +2834 42 +2835 42 43 43 60 67 82 +2836 42 43 70 +2837 42 +2838 42 +2839 42 53 +2840 42 +2841 42 +2842 42 42 +2843 42 80 +2844 42 82 82 +2845 42 42 +2846 42 +2847 42 +2848 42 43 43 +2849 42 79 +2850 42 +2851 42 80 82 +2852 42 80 +2853 42 74 81 +2854 42 +2855 42 81 82 +2856 42 +2857 42 +2858 42 +2859 42 +2860 42 +2861 42 80 81 +2862 42 43 82 +2863 42 68 71 +2864 42 72 78 +2865 42 54 55 76 76 78 +2866 43 +2867 43 +2868 43 +2869 43 +2870 43 79 +2871 43 +2872 43 +2873 43 +2874 43 +2875 43 77 +2876 43 +2877 43 43 +2878 43 +2879 43 +2880 43 43 +2881 43 +2882 43 +2883 43 +2884 43 +2885 43 +2886 43 80 81 +2887 43 +2888 43 +2889 43 +2890 43 68 70 74 +2891 43 43 47 49 +2892 43 +2893 43 +2894 43 46 +2895 43 +2896 43 +2897 43 +2898 43 +2899 43 +2900 43 80 81 82 +2901 43 +2902 43 +2903 43 +2904 43 +2905 43 +2906 43 +2907 43 76 78 +2908 43 +2909 43 +2910 43 +2911 43 63 +2912 43 +2913 43 +2914 43 68 +2915 43 +2916 43 +2917 43 +2918 43 +2919 43 +2920 43 45 +2921 43 50 +2922 43 +2923 43 +2924 43 +2925 43 +2926 43 51 +2927 43 +2928 43 +2929 43 +2930 43 48 73 +2931 43 +2932 43 +2933 43 +2934 45 75 76 +2935 45 45 +2936 45 +2937 45 +2938 45 +2939 45 +2940 45 +2941 45 +2942 45 +2943 45 +2944 45 +2945 45 +2946 45 +2947 45 51 51 +2948 45 +2949 45 53 56 68 +2950 45 +2951 45 +2952 45 46 +2953 45 +2954 45 +2955 45 +2956 45 +2957 45 +2958 45 48 +2959 45 +2960 45 +2961 45 +2962 45 45 +2963 45 +2964 45 +2965 45 +2966 45 +2967 45 +2968 45 +2969 45 +2970 45 48 +2971 45 +2972 45 +2973 45 +2974 45 +2975 46 46 53 62 +2976 46 +2977 46 +2978 46 +2979 46 +2980 46 +2981 46 +2982 46 51 +2983 46 +2984 46 73 +2985 46 +2986 46 +2987 46 46 +2988 46 +2989 46 +2990 46 +2991 46 +2992 46 50 50 50 +2993 46 52 71 +2994 46 61 +2995 46 47 +2996 46 +2997 46 +2998 46 +2999 46 53 +3000 46 68 +3001 46 +3002 46 +3003 46 70 +3004 46 +3005 46 +3006 46 65 76 +3007 46 +3008 46 47 +3009 46 +3010 46 +3011 46 +3012 46 +3013 46 +3014 46 +3015 46 +3016 47 +3017 47 +3018 47 +3019 47 +3020 47 79 79 +3021 47 47 +3022 47 +3023 47 49 +3024 47 +3025 47 +3026 47 +3027 47 +3028 47 63 64 71 +3029 47 +3030 47 +3031 47 48 +3032 47 +3033 47 +3034 47 48 +3035 47 +3036 47 64 82 +3037 47 +3038 47 +3039 47 50 +3040 47 +3041 47 50 51 +3042 47 +3043 47 +3044 47 +3045 47 +3046 47 +3047 47 70 +3048 47 +3049 47 +3050 47 +3051 47 48 +3052 47 +3053 47 +3054 47 49 +3055 47 +3056 48 +3057 48 +3058 48 +3059 48 +3060 48 +3061 48 +3062 48 65 +3063 48 +3064 48 +3065 48 +3066 48 64 77 +3067 48 +3068 48 +3069 48 +3070 48 76 77 +3071 48 +3072 48 +3073 48 49 +3074 48 +3075 48 49 64 68 +3076 48 +3077 48 49 +3078 48 +3079 48 +3080 48 +3081 48 49 +3082 48 +3083 48 49 +3084 48 +3085 48 +3086 48 +3087 48 +3088 48 +3089 49 75 +3090 49 49 49 78 +3091 49 +3092 49 59 68 70 +3093 49 +3094 49 +3095 49 +3096 49 +3097 49 55 58 61 63 +3098 49 +3099 49 76 +3100 49 +3101 49 +3102 49 +3103 49 59 +3104 49 53 +3105 49 50 +3106 49 64 70 +3107 49 +3108 49 +3109 49 51 65 +3110 49 +3111 49 +3112 49 +3113 49 +3114 49 49 +3115 49 51 58 72 76 +3116 49 +3117 49 +3118 49 +3119 49 +3120 49 +3121 49 +3122 49 +3123 49 +3124 50 51 71 +3125 50 +3126 50 51 +3127 50 +3128 50 51 +3129 50 52 +3130 50 51 +3131 50 +3132 50 +3133 50 68 +3134 50 55 +3135 50 69 +3136 50 +3137 50 50 +3138 50 +3139 50 +3140 50 +3141 50 +3142 50 +3143 50 82 +3144 50 50 +3145 50 64 +3146 50 +3147 50 +3148 50 +3149 50 56 76 +3150 50 80 +3151 50 +3152 50 +3153 50 +3154 50 69 +3155 50 +3156 50 +3157 50 +3158 50 +3159 50 54 77 +3160 50 +3161 50 70 +3162 51 +3163 51 +3164 51 67 +3165 51 +3166 51 60 61 64 67 78 +3167 51 +3168 51 52 53 +3169 51 +3170 51 +3171 51 52 +3172 51 +3173 51 52 52 58 59 80 +3174 51 +3175 51 +3176 51 +3177 51 +3178 51 51 52 59 +3179 51 +3180 51 +3181 51 52 +3182 51 +3183 51 52 +3184 51 +3185 51 +3186 51 65 +3187 51 75 +3188 51 +3189 51 +3190 51 +3191 51 +3192 51 +3193 51 +3194 51 +3195 51 +3196 51 66 67 +3197 51 +3198 51 52 +3199 51 +3200 52 +3201 52 +3202 52 +3203 52 +3204 52 +3205 52 +3206 52 +3207 52 70 70 72 78 81 +3208 52 +3209 52 75 +3210 52 +3211 52 +3212 52 53 54 60 +3213 52 53 60 68 +3214 52 53 53 53 55 64 77 +3215 52 +3216 52 53 +3217 52 71 80 +3218 52 64 +3219 52 +3220 52 54 +3221 52 +3222 52 +3223 53 53 55 55 55 +3224 53 +3225 53 +3226 53 +3227 53 +3228 53 +3229 53 +3230 53 +3231 53 +3232 53 +3233 53 60 67 +3234 53 +3235 53 53 +3236 53 +3237 53 +3238 53 82 82 +3239 53 +3240 53 +3241 53 +3242 53 +3243 53 +3244 53 +3245 53 +3246 53 +3247 53 65 +3248 53 +3249 53 +3250 53 +3251 53 +3252 53 +3253 54 55 62 +3254 54 +3255 54 +3256 54 64 65 72 73 77 78 81 +3257 54 +3258 54 +3259 54 +3260 54 +3261 54 +3262 54 +3263 54 +3264 54 +3265 54 +3266 55 +3267 55 56 +3268 55 +3269 55 73 +3270 55 +3271 55 +3272 55 +3273 55 +3274 55 +3275 55 +3276 55 73 +3277 55 +3278 55 +3279 55 +3280 55 56 +3281 55 62 +3282 55 68 77 +3283 55 +3284 55 +3285 55 +3286 55 +3287 55 +3288 55 +3289 55 56 77 +3290 55 +3291 55 +3292 55 +3293 55 +3294 55 +3295 55 56 +3296 55 +3297 55 +3298 55 +3299 55 +3300 55 +3301 55 +3302 56 +3303 56 +3304 56 +3305 56 +3306 56 +3307 56 59 +3308 56 +3309 56 +3310 56 +3311 56 +3312 56 +3313 56 +3314 56 +3315 56 57 57 57 59 61 61 77 77 79 82 +3316 56 +3317 56 +3318 56 +3319 57 +3320 57 +3321 57 +3322 57 +3323 57 +3324 57 +3325 57 +3326 57 +3327 57 +3328 57 +3329 57 +3330 57 +3331 57 +3332 57 +3333 57 +3334 57 +3335 57 +3336 57 58 60 61 63 79 79 80 +3337 57 +3338 57 58 58 60 78 78 79 80 +3339 57 +3340 57 57 +3341 57 +3342 58 64 64 65 66 67 69 +3343 58 +3344 58 +3345 58 +3346 58 +3347 58 +3348 58 +3349 58 +3350 58 +3351 58 +3352 58 +3353 58 +3354 58 +3355 58 62 +3356 58 +3357 58 +3358 58 +3359 58 71 +3360 58 +3361 58 +3362 58 58 68 +3363 58 +3364 58 +3365 58 +3366 58 +3367 58 +3368 58 +3369 58 +3370 58 +3371 58 +3372 58 +3373 58 +3374 58 +3375 58 +3376 58 +3377 58 +3378 58 +3379 58 +3380 58 +3381 58 58 +3382 58 +3383 58 +3384 58 +3385 58 +3386 58 +3387 58 +3388 58 +3389 58 64 64 68 71 +3390 58 +3391 58 61 +3392 58 +3393 58 +3394 58 +3395 58 +3396 58 +3397 58 +3398 58 +3399 58 +3400 58 +3401 58 +3402 58 +3403 58 58 +3404 58 +3405 58 +3406 58 +3407 58 58 +3408 58 +3409 58 +3410 58 +3411 58 +3412 58 +3413 58 +3414 58 +3415 58 +3416 58 +3417 58 +3418 58 +3419 58 +3420 58 +3421 58 +3422 58 +3423 58 +3424 58 +3425 58 +3426 58 +3427 58 +3428 58 +3429 58 +3430 58 +3431 58 +3432 58 +3433 58 +3434 58 +3435 59 +3436 59 +3437 59 +3438 59 +3439 59 +3440 59 +3441 59 60 +3442 59 +3443 59 +3444 59 +3445 59 71 74 +3446 59 +3447 59 61 +3448 59 +3449 59 +3450 59 +3451 59 +3452 59 +3453 59 +3454 59 +3455 59 +3456 59 +3457 59 +3458 59 60 +3459 59 +3460 59 +3461 59 59 +3462 59 64 +3463 59 +3464 59 +3465 59 +3466 59 +3467 59 +3468 59 59 +3469 59 +3470 59 +3471 59 +3472 59 +3473 59 +3474 59 +3475 59 +3476 59 +3477 59 +3478 59 +3479 59 +3480 59 +3481 59 +3482 59 +3483 59 +3484 59 +3485 60 +3486 60 +3487 60 +3488 60 +3489 60 +3490 60 +3491 60 +3492 60 +3493 60 60 +3494 60 +3495 60 +3496 60 +3497 60 +3498 60 +3499 60 75 +3500 60 70 +3501 60 +3502 60 +3503 60 +3504 60 +3505 60 +3506 60 +3507 60 +3508 60 +3509 60 +3510 60 73 +3511 60 +3512 60 61 +3513 60 +3514 60 +3515 60 +3516 60 +3517 60 +3518 60 +3519 60 +3520 60 +3521 60 +3522 60 +3523 60 +3524 60 +3525 60 +3526 60 +3527 60 +3528 61 +3529 61 80 +3530 61 62 +3531 61 62 63 63 64 75 82 82 +3532 61 +3533 61 +3534 61 +3535 61 +3536 61 +3537 61 +3538 61 +3539 61 +3540 61 +3541 61 +3542 61 +3543 61 +3544 61 +3545 61 74 +3546 61 +3547 61 +3548 61 +3549 61 +3550 61 +3551 61 +3552 61 62 +3553 61 +3554 61 +3555 61 +3556 61 +3557 61 +3558 61 63 69 73 +3559 61 +3560 62 +3561 62 +3562 62 +3563 62 +3564 62 +3565 62 +3566 62 +3567 62 +3568 62 +3569 62 63 +3570 62 +3571 62 +3572 62 +3573 62 +3574 62 63 +3575 62 +3576 62 +3577 62 +3578 62 +3579 62 67 +3580 62 +3581 62 +3582 63 +3583 63 +3584 63 +3585 63 +3586 63 +3587 63 +3588 63 +3589 63 63 72 +3590 63 +3591 63 +3592 63 +3593 63 +3594 63 +3595 64 +3596 64 +3597 64 +3598 64 +3599 64 +3600 64 77 +3601 64 +3602 64 +3603 64 +3604 64 +3605 64 +3606 64 +3607 64 +3608 64 +3609 64 +3610 64 70 71 71 +3611 64 70 +3612 64 +3613 64 +3614 64 65 70 73 75 80 +3615 64 +3616 64 66 +3617 64 +3618 64 68 70 72 75 76 +3619 64 +3620 64 +3621 64 +3622 64 +3623 64 +3624 64 81 +3625 64 +3626 64 +3627 64 65 +3628 64 +3629 64 +3630 64 +3631 64 +3632 64 +3633 64 67 +3634 64 +3635 64 +3636 64 +3637 64 +3638 64 79 +3639 64 +3640 64 +3641 64 +3642 64 64 +3643 64 +3644 64 +3645 64 +3646 64 +3647 64 +3648 64 +3649 64 +3650 64 +3651 64 +3652 64 +3653 64 +3654 64 +3655 64 +3656 65 67 70 71 +3657 65 66 +3658 65 +3659 65 +3660 65 66 +3661 65 +3662 65 +3663 65 66 +3664 65 +3665 65 69 +3666 65 +3667 65 +3668 65 +3669 65 +3670 65 66 +3671 65 +3672 65 +3673 65 +3674 65 +3675 65 +3676 65 +3677 65 +3678 65 +3679 65 +3680 65 +3681 65 +3682 65 +3683 65 +3684 65 +3685 65 65 66 +3686 65 +3687 65 +3688 65 +3689 65 +3690 65 +3691 65 +3692 65 +3693 65 +3694 65 +3695 65 +3696 65 +3697 66 +3698 66 +3699 66 67 +3700 66 +3701 66 +3702 66 +3703 66 +3704 66 +3705 66 +3706 66 +3707 66 +3708 66 +3709 66 +3710 66 72 +3711 66 +3712 66 +3713 66 81 +3714 66 +3715 66 +3716 66 +3717 66 +3718 66 +3719 66 +3720 66 68 69 70 71 72 +3721 66 +3722 66 +3723 66 +3724 66 +3725 66 71 +3726 66 +3727 66 +3728 66 +3729 66 +3730 66 +3731 66 +3732 66 +3733 66 +3734 66 68 +3735 66 67 68 69 +3736 66 +3737 66 +3738 66 +3739 66 66 66 +3740 66 +3741 66 +3742 66 +3743 66 +3744 66 +3745 66 +3746 66 +3747 66 +3748 66 +3749 66 +3750 66 +3751 66 +3752 66 +3753 66 +3754 67 +3755 67 +3756 67 +3757 67 +3758 67 +3759 67 +3760 67 +3761 67 68 70 72 +3762 67 68 +3763 67 +3764 67 +3765 67 +3766 67 68 80 82 +3767 67 +3768 67 +3769 67 67 +3770 67 +3771 67 +3772 67 +3773 67 +3774 67 +3775 67 +3776 67 +3777 67 +3778 67 +3779 67 +3780 67 68 70 +3781 67 +3782 67 71 +3783 67 +3784 67 +3785 67 +3786 67 +3787 67 +3788 67 +3789 67 +3790 67 +3791 67 +3792 67 +3793 67 69 70 +3794 67 +3795 67 +3796 67 +3797 67 +3798 67 +3799 67 +3800 67 +3801 67 68 76 +3802 67 +3803 67 82 +3804 68 +3805 68 +3806 68 +3807 68 +3808 68 +3809 68 +3810 68 +3811 68 +3812 68 +3813 68 +3814 68 +3815 68 +3816 68 +3817 68 +3818 68 69 +3819 68 +3820 68 +3821 68 +3822 68 +3823 68 +3824 68 +3825 68 +3826 68 +3827 68 69 +3828 68 75 +3829 68 78 +3830 68 +3831 68 +3832 68 68 70 79 +3833 68 +3834 68 +3835 68 +3836 68 +3837 68 +3838 68 +3839 68 +3840 68 +3841 68 +3842 68 +3843 68 +3844 68 +3845 68 +3846 68 +3847 68 +3848 68 +3849 68 +3850 68 +3851 68 +3852 68 +3853 68 +3854 68 +3855 68 +3856 68 +3857 68 +3858 68 +3859 68 +3860 68 +3861 68 +3862 68 +3863 68 +3864 68 +3865 68 +3866 68 +3867 68 +3868 68 +3869 68 +3870 68 +3871 68 +3872 68 +3873 68 +3874 68 +3875 68 +3876 68 +3877 68 +3878 68 +3879 68 +3880 68 +3881 69 71 +3882 69 +3883 69 +3884 69 +3885 69 +3886 69 +3887 69 +3888 69 +3889 69 71 72 77 +3890 69 +3891 69 +3892 69 +3893 69 +3894 69 72 +3895 69 +3896 69 +3897 69 +3898 69 +3899 69 +3900 69 +3901 69 +3902 69 +3903 69 +3904 69 +3905 69 +3906 69 +3907 69 +3908 69 +3909 69 +3910 69 +3911 69 +3912 69 +3913 69 77 +3914 69 69 +3915 69 72 +3916 69 +3917 69 +3918 69 +3919 69 +3920 69 +3921 69 +3922 69 +3923 69 +3924 69 +3925 69 +3926 69 +3927 69 +3928 69 72 74 +3929 69 +3930 69 +3931 69 +3932 69 +3933 69 +3934 69 +3935 69 +3936 69 +3937 69 69 71 71 +3938 69 +3939 69 +3940 69 +3941 69 +3942 69 +3943 69 +3944 69 +3945 69 +3946 69 +3947 69 +3948 69 +3949 69 +3950 69 70 70 +3951 69 +3952 69 +3953 69 +3954 69 +3955 69 +3956 69 +3957 69 +3958 69 +3959 69 +3960 69 +3961 69 +3962 69 +3963 69 +3964 69 +3965 69 +3966 69 +3967 69 +3968 69 +3969 69 +3970 69 +3971 69 +3972 70 +3973 70 71 +3974 70 +3975 70 +3976 70 +3977 70 +3978 70 +3979 70 +3980 70 +3981 70 +3982 70 +3983 70 +3984 70 +3985 70 +3986 70 +3987 70 +3988 70 +3989 70 +3990 70 +3991 70 +3992 70 +3993 70 +3994 70 +3995 70 +3996 70 +3997 70 +3998 70 +3999 70 +4000 70 +4001 70 +4002 70 +4003 70 +4004 70 +4005 70 +4006 70 +4007 70 +4008 70 +4009 70 +4010 70 +4011 70 70 +4012 70 +4013 70 71 +4014 70 +4015 70 +4016 70 +4017 70 +4018 70 +4019 70 +4020 70 +4021 70 +4022 70 +4023 70 +4024 70 +4025 70 +4026 70 75 +4027 70 +4028 70 +4029 70 70 72 +4030 70 +4031 70 +4032 70 +4033 70 +4034 70 +4035 70 71 72 +4036 70 +4037 70 +4038 70 +4039 70 75 +4040 70 +4041 70 +4042 70 +4043 70 +4044 70 +4045 70 +4046 70 +4047 70 +4048 70 +4049 70 +4050 70 +4051 70 +4052 70 +4053 70 +4054 70 +4055 70 +4056 70 71 +4057 70 +4058 70 +4059 70 +4060 70 +4061 70 +4062 70 +4063 70 +4064 71 +4065 71 +4066 71 +4067 71 71 +4068 71 +4069 71 +4070 71 +4071 71 +4072 71 +4073 71 +4074 71 +4075 71 +4076 71 +4077 71 71 +4078 71 +4079 71 +4080 71 +4081 71 71 +4082 71 72 +4083 71 +4084 71 +4085 71 +4086 71 +4087 71 +4088 71 +4089 71 +4090 71 +4091 71 +4092 71 +4093 71 +4094 71 +4095 71 +4096 71 +4097 71 +4098 71 +4099 71 +4100 71 +4101 71 +4102 71 +4103 71 +4104 71 +4105 71 +4106 71 +4107 71 71 +4108 71 +4109 71 +4110 71 72 +4111 71 +4112 71 +4113 71 +4114 71 +4115 71 +4116 71 +4117 71 +4118 71 +4119 71 +4120 71 +4121 71 +4122 71 71 +4123 71 +4124 71 +4125 71 +4126 71 +4127 71 +4128 71 +4129 71 +4130 71 +4131 71 +4132 71 +4133 71 71 +4134 71 +4135 71 +4136 71 +4137 71 +4138 71 +4139 71 +4140 71 +4141 71 +4142 71 +4143 71 +4144 71 +4145 71 +4146 71 +4147 71 +4148 71 +4149 71 +4150 72 +4151 72 +4152 72 +4153 72 +4154 72 +4155 72 +4156 72 +4157 72 72 +4158 72 +4159 72 +4160 72 +4161 72 +4162 72 +4163 72 +4164 72 74 +4165 72 +4166 72 72 73 +4167 72 +4168 72 +4169 72 +4170 72 +4171 72 +4172 72 +4173 72 +4174 72 +4175 72 +4176 72 +4177 72 75 +4178 72 72 +4179 72 +4180 72 +4181 72 +4182 72 +4183 72 +4184 72 +4185 72 +4186 72 +4187 72 +4188 72 72 +4189 72 +4190 72 73 +4191 72 +4192 72 +4193 72 +4194 72 +4195 72 +4196 72 +4197 72 +4198 72 +4199 72 +4200 72 +4201 72 +4202 72 +4203 72 +4204 73 73 +4205 73 +4206 73 +4207 73 +4208 73 +4209 73 +4210 73 73 +4211 73 +4212 73 74 +4213 73 74 +4214 73 74 +4215 73 +4216 73 81 +4217 73 +4218 73 +4219 73 +4220 73 74 78 +4221 73 +4222 73 +4223 73 +4224 73 73 +4225 73 +4226 73 +4227 73 78 +4228 73 +4229 73 +4230 73 +4231 73 +4232 73 +4233 73 +4234 73 +4235 73 +4236 73 +4237 73 +4238 73 +4239 73 +4240 73 +4241 74 +4242 74 +4243 74 +4244 74 +4245 74 +4246 74 +4247 74 +4248 74 +4249 74 +4250 74 +4251 74 +4252 74 +4253 74 +4254 74 +4255 74 +4256 74 +4257 74 +4258 74 +4259 75 +4260 75 76 +4261 75 +4262 75 +4263 75 +4264 75 +4265 75 78 78 +4266 75 +4267 75 +4268 75 +4269 75 +4270 75 +4271 75 78 +4272 75 +4273 75 +4274 75 +4275 75 +4276 75 +4277 75 +4278 75 +4279 75 79 +4280 75 +4281 75 76 +4282 75 +4283 75 +4284 75 +4285 75 +4286 75 +4287 75 +4288 75 +4289 76 +4290 76 +4291 76 +4292 76 77 +4293 76 +4294 76 +4295 76 +4296 76 82 +4297 76 +4298 76 76 78 +4299 76 +4300 76 +4301 76 77 +4302 76 +4303 76 +4304 76 +4305 76 77 78 78 +4306 76 +4307 76 77 +4308 76 +4309 76 +4310 76 +4311 76 +4312 76 +4313 76 +4314 76 +4315 77 +4316 77 +4317 77 +4318 77 +4319 77 +4320 77 +4321 77 +4322 77 +4323 77 80 +4324 77 +4325 77 +4326 77 +4327 77 +4328 77 +4329 77 +4330 77 +4331 77 +4332 77 +4333 77 +4334 77 +4335 77 +4336 77 +4337 77 +4338 77 78 78 +4339 77 78 +4340 77 +4341 77 +4342 77 +4343 77 +4344 77 +4345 77 +4346 77 +4347 77 +4348 77 +4349 77 +4350 78 +4351 78 +4352 78 +4353 78 +4354 78 +4355 78 +4356 78 +4357 78 +4358 78 +4359 78 +4360 78 +4361 78 +4362 78 +4363 78 79 80 81 +4364 78 +4365 78 +4366 78 +4367 78 +4368 78 +4369 78 +4370 78 78 +4371 78 +4372 78 +4373 78 +4374 78 +4375 78 +4376 78 +4377 78 +4378 78 +4379 78 +4380 78 +4381 78 +4382 78 +4383 78 +4384 78 +4385 78 +4386 78 +4387 78 +4388 78 +4389 78 +4390 78 +4391 78 +4392 78 +4393 78 +4394 78 +4395 78 +4396 78 +4397 79 +4398 79 80 +4399 79 +4400 79 +4401 79 +4402 79 +4403 79 +4404 79 80 +4405 79 81 82 +4406 79 +4407 79 +4408 79 +4409 79 +4410 79 80 80 81 +4411 79 +4412 79 +4413 79 +4414 79 +4415 79 +4416 79 79 +4417 79 80 +4418 79 +4419 79 +4420 79 +4421 79 +4422 79 80 +4423 79 +4424 79 +4425 79 +4426 79 +4427 79 +4428 79 +4429 79 +4430 79 +4431 79 +4432 79 80 +4433 79 +4434 79 +4435 79 +4436 79 +4437 79 79 +4438 79 +4439 79 +4440 79 +4441 79 +4442 79 +4443 79 +4444 79 +4445 79 +4446 79 +4447 79 +4448 79 +4449 79 +4450 79 +4451 79 +4452 79 79 +4453 79 +4454 80 +4455 80 +4456 80 +4457 80 +4458 80 +4459 80 +4460 80 +4461 80 +4462 80 82 +4463 80 +4464 80 +4465 80 +4466 80 +4467 80 +4468 80 +4469 80 +4470 80 +4471 80 +4472 80 +4473 80 80 80 +4474 80 +4475 80 +4476 80 +4477 80 +4478 80 +4479 80 +4480 80 +4481 80 +4482 80 +4483 80 +4484 80 81 81 +4485 80 +4486 80 +4487 80 +4488 80 +4489 80 +4490 80 +4491 80 +4492 80 +4493 80 +4494 80 +4495 80 +4496 80 +4497 80 +4498 80 +4499 80 +4500 80 +4501 80 +4502 80 82 +4503 81 +4504 81 +4505 81 +4506 81 82 +4507 81 +4508 81 +4509 81 82 82 +4510 81 +4511 81 +4512 81 +4513 81 81 +4514 81 +4515 81 +4516 81 82 +4517 81 +4518 81 +4519 81 +4520 81 +4521 81 +4522 81 +4523 81 82 +4524 81 +4525 81 +4526 81 +4527 81 +4528 81 +4529 81 +4530 81 +4531 81 +4532 81 +4533 82 82 +4534 82 +4535 82 +4536 82 +4537 82 +4538 82 +4539 82 +4540 82 +4541 82 +4542 82 +4543 82 +4544 82 +4545 82 +4546 82 +4547 82 +4548 82 +4549 82 +4550 82 +4551 82 +4552 82 +4553 82 +4554 82 diff --git a/Matlab/Tests/090306-3_db-Signatures.txt b/Matlab/Tests/090306-3_db-Signatures.txt new file mode 100644 index 0000000000..69e2bd729f --- /dev/null +++ b/Matlab/Tests/090306-3_db-Signatures.txt @@ -0,0 +1,84 @@ +SignatureID WordsID... +-1 5 8 9 21 22 23 27 30 32 34 36 40 45 53 61 62 64 67 68 77 85 86 97 98 99 100 103 106 122 124 125 127 129 131 143 157 161 164 168 169 172 175 181 182 187 196 197 208 210 217 218 219 220 234 235 237 244 252 286 307 308 310 315 327 328 346 347 350 355 362 372 373 387 393 394 398 404 412 423 428 429 441 442 456 465 470 476 495 496 498 506 520 558 572 584 585 587 596 620 634 637 643 646 647 658 667 668 681 709 721 726 741 766 777 785 790 791 793 808 809 880 896 906 908 919 933 940 954 958 974 981 1002 1023 1026 1058 1060 1071 1074 1097 1146 1152 1166 1215 1217 1229 1334 1341 1387 1478 1510 1539 1549 1566 1601 1624 1649 1693 1814 2046 2141 2424 2442 2674 +1 1 2 3 4 4 5 6 7 7 7 8 8 8 9 10 11 12 13 14 15 16 17 18 19 19 20 21 22 23 23 23 23 24 25 26 27 27 28 29 30 30 31 31 32 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 46 47 48 49 49 49 50 51 52 53 53 54 54 55 56 57 58 59 60 61 61 62 63 64 65 66 67 68 69 70 71 71 72 73 74 75 76 77 78 79 79 80 81 82 83 84 85 85 86 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 105 106 107 108 109 109 110 111 111 112 113 114 115 +2 8 8 8 9 9 18 18 19 19 21 21 21 21 23 23 23 24 26 27 27 27 32 36 36 39 40 46 61 61 61 62 62 63 64 65 67 67 67 68 68 68 69 69 72 79 79 80 81 86 87 95 97 113 116 117 118 119 120 121 122 122 123 124 124 125 126 127 128 129 130 131 132 133 134 135 136 137 137 138 139 140 141 142 142 143 144 145 146 147 148 149 150 151 152 153 153 154 155 156 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 +3 8 22 24 30 32 40 62 63 64 67 67 67 81 85 85 97 98 98 122 127 127 129 129 129 129 131 132 142 143 155 159 163 181 181 181 198 209 217 217 217 218 218 219 220 221 221 222 222 223 224 225 226 227 227 228 229 229 230 231 232 233 234 235 236 237 238 238 239 240 241 242 242 243 244 244 244 245 246 247 248 249 250 251 252 253 254 255 256 256 257 258 259 260 261 262 263 264 +4 2 8 8 23 37 64 67 67 67 67 67 67 67 74 85 98 124 124 125 125 127 127 129 131 131 132 132 143 157 159 161 161 175 175 175 181 182 186 187 187 201 208 210 217 217 218 218 218 218 219 220 220 220 220 221 221 225 227 228 230 234 234 234 234 234 234 234 236 241 242 244 244 244 244 244 245 247 252 261 265 266 267 267 268 269 269 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 283 284 285 286 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 302 303 303 304 304 305 306 306 307 308 308 309 310 310 311 311 312 313 314 315 316 317 318 319 320 321 322 323 324 324 325 326 327 328 328 329 330 331 332 333 334 334 334 335 336 337 338 339 340 341 342 343 343 344 344 345 346 346 347 348 349 350 350 350 350 351 352 353 354 355 356 357 358 359 360 361 362 363 +5 8 9 9 18 30 30 32 32 32 40 45 77 77 98 106 108 121 122 122 122 122 122 122 125 130 157 157 168 170 181 182 212 218 218 219 234 234 234 252 252 257 257 269 286 304 307 307 346 355 355 357 364 365 366 367 368 369 369 370 371 372 372 373 374 375 376 377 378 379 380 381 382 382 383 384 385 386 386 387 388 389 390 391 392 393 393 394 395 396 396 397 398 399 400 401 402 403 404 404 405 406 407 408 409 409 410 411 412 413 414 415 416 417 418 419 420 420 421 422 423 423 423 423 424 425 426 426 427 428 429 429 430 431 432 433 434 435 436 436 437 438 439 440 441 442 442 443 444 444 445 446 447 448 449 450 451 452 453 454 455 456 456 457 457 458 459 460 461 461 462 463 464 464 465 466 467 468 469 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 +6 23 23 29 30 30 40 100 129 129 142 142 142 143 168 175 181 217 217 217 247 257 257 307 307 307 325 325 327 346 346 369 369 370 371 372 372 372 372 372 373 377 379 382 385 387 393 394 396 398 402 404 405 409 412 417 418 419 428 430 438 442 442 451 453 456 456 456 463 466 467 486 487 488 489 490 491 492 493 494 495 496 497 498 498 499 500 501 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 531 532 533 534 535 536 537 538 +7 9 29 30 45 49 53 64 67 77 97 106 122 125 129 129 129 129 129 130 175 181 181 181 181 181 181 181 217 217 217 221 221 228 231 234 234 237 247 285 307 309 314 325 355 363 370 372 372 372 382 385 387 393 393 400 412 416 418 423 426 429 438 438 442 442 442 456 457 460 463 467 467 486 490 492 493 498 499 504 506 506 508 508 511 516 526 532 534 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 585 586 587 588 589 590 591 592 593 594 595 596 596 597 598 599 600 601 602 603 604 605 606 607 607 608 609 609 610 611 612 613 614 +8 7 9 9 23 32 32 32 32 67 67 67 67 85 95 121 124 124 125 129 129 130 175 175 181 181 181 181 182 182 184 189 190 196 197 197 208 210 221 234 234 234 244 304 308 308 310 328 329 329 330 343 346 353 355 355 362 362 362 363 367 372 378 387 387 393 408 408 414 414 423 427 430 442 448 450 457 467 470 470 486 495 498 504 506 510 511 516 520 551 555 558 558 558 572 572 572 576 582 582 584 584 604 615 616 617 618 619 620 621 622 623 624 625 626 627 628 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 681 682 683 684 685 686 687 688 689 689 690 691 692 693 693 694 695 695 696 697 698 699 699 700 701 702 703 704 705 +9 15 32 32 32 67 98 122 127 142 175 181 218 218 234 235 244 244 244 244 247 307 308 308 310 310 315 315 327 328 328 329 362 373 377 387 389 393 393 396 397 398 398 400 404 404 404 408 413 429 441 448 456 467 470 470 480 493 498 558 572 572 584 589 597 615 615 619 619 620 620 621 621 626 628 634 634 636 640 644 651 653 656 656 659 660 662 664 664 665 666 668 668 672 680 682 706 707 708 709 710 711 712 713 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 +10 8 9 23 23 29 29 30 30 30 30 32 32 36 40 64 67 67 125 125 129 129 130 161 172 172 172 175 181 181 187 196 197 210 210 220 221 234 234 234 234 234 235 235 237 244 244 244 308 308 320 327 328 328 338 338 338 338 343 346 347 347 362 373 373 384 389 412 441 442 467 486 495 495 515 520 528 533 572 589 596 596 597 613 613 620 620 634 636 637 643 646 651 681 685 685 685 685 685 695 699 699 702 709 717 717 725 726 731 732 734 735 737 741 743 746 756 757 758 759 760 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 820 821 822 +11 4 5 8 8 8 15 23 29 30 30 32 53 61 67 67 67 77 85 85 85 85 85 86 118 120 125 133 142 175 175 181 182 182 187 206 210 219 234 235 237 244 244 251 252 308 308 308 308 308 308 308 308 310 315 343 350 355 373 373 387 399 410 412 412 412 423 428 429 438 441 441 495 512 551 555 558 558 558 584 584 585 589 607 608 613 615 620 620 637 637 646 646 646 647 649 652 656 696 697 709 716 720 741 746 759 763 763 766 766 766 775 775 777 782 785 785 785 785 791 791 791 791 791 792 795 797 797 807 808 808 809 809 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 856 857 858 859 860 861 862 863 864 865 866 867 867 868 869 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 +12 4 5 5 7 9 12 23 29 30 30 32 32 32 32 45 63 64 67 98 106 124 125 127 127 130 131 168 172 172 175 175 175 175 175 175 187 197 206 206 234 234 244 244 244 244 247 291 308 308 308 308 308 344 351 362 372 373 389 404 423 429 441 444 448 476 495 498 498 520 543 550 552 558 558 584 637 637 643 643 646 651 656 658 667 681 681 716 717 721 721 728 744 744 744 746 759 762 766 769 769 769 780 785 790 793 808 808 808 835 849 854 880 884 886 888 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 907 908 908 908 909 909 909 910 911 912 913 914 915 916 917 918 919 920 921 922 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 944 945 946 947 948 949 950 951 +13 8 8 9 9 18 30 32 67 67 67 67 85 106 108 124 124 124 124 125 131 172 175 208 215 217 218 218 231 234 235 257 269 285 286 305 305 308 320 329 330 334 387 389 394 394 394 441 441 448 448 465 471 476 486 506 520 545 558 584 584 585 596 596 597 626 637 637 637 647 667 681 681 685 685 702 717 760 766 769 769 785 790 793 797 809 810 829 840 880 892 896 896 898 898 903 906 908 911 911 916 916 919 922 936 940 944 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 969 970 971 972 973 974 975 975 976 977 978 979 979 980 981 981 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1011 1012 1013 1014 +14 27 67 97 100 172 172 244 312 329 330 372 373 396 404 412 412 412 427 495 498 506 558 558 637 667 702 717 760 790 808 832 896 896 896 896 919 954 981 981 981 995 996 996 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 +15 5 9 9 9 9 9 13 30 64 64 64 67 67 67 67 67 67 67 67 67 67 67 67 95 118 125 125 125 125 125 129 129 164 168 172 172 172 172 175 181 181 181 181 181 199 218 220 234 307 327 327 328 346 346 346 355 355 355 362 362 362 362 362 362 372 372 373 373 373 412 412 476 476 539 558 558 558 558 558 558 558 558 558 558 571 584 584 584 585 585 590 647 658 658 658 658 667 721 721 721 721 721 721 766 766 791 880 880 880 880 880 880 908 926 954 958 1019 1023 1027 1033 1035 1035 1035 1036 1037 1038 1039 1040 1041 1042 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 +16 8 8 8 9 9 9 21 21 23 30 30 30 30 30 30 32 45 64 64 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 67 125 125 125 125 125 125 125 125 130 130 161 161 161 172 172 172 172 172 172 172 175 175 175 199 199 219 220 220 234 234 234 234 234 267 307 310 310 316 316 327 327 327 328 343 343 343 346 346 346 355 355 355 362 362 362 362 362 362 362 362 362 362 362 362 363 372 373 373 373 373 373 373 373 394 498 509 509 558 558 558 558 558 558 558 558 558 558 558 558 558 558 558 558 558 558 558 558 558 558 558 584 584 584 584 585 680 680 721 721 721 721 721 722 722 851 880 880 880 880 926 1049 1058 1058 1068 1074 1074 1074 1074 1074 1074 1074 1074 1074 1074 1074 1074 1077 1078 1079 1080 1081 1082 1083 1084 1085 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 +17 +18 3 8 8 22 23 23 23 28 28 34 45 51 51 51 53 63 99 100 122 125 143 157 168 168 173 182 187 187 187 187 206 220 244 301 347 372 373 387 394 394 396 398 423 426 428 486 513 539 587 589 667 674 695 709 726 785 785 810 863 908 908 919 922 936 1019 1034 1082 1104 1105 1106 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1119 1120 1121 1122 1123 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1146 1147 1148 1149 1150 1151 1152 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1169 1170 1171 1172 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 +19 36 63 121 121 131 133 142 143 143 168 168 172 172 187 218 219 238 239 310 312 362 387 387 486 584 587 609 647 652 680 696 718 782 981 984 1019 1056 1056 1089 1107 1117 1131 1133 1142 1142 1145 1146 1146 1153 1158 1158 1160 1166 1166 1170 1172 1173 1181 1182 1184 1194 1212 1227 1228 1229 1230 1231 1232 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1278 1279 +20 8 8 9 9 10 13 22 22 23 67 67 67 67 67 83 83 99 127 129 134 134 164 175 182 186 188 210 218 219 285 301 307 308 308 308 310 340 346 355 362 373 394 427 506 558 558 587 587 596 634 637 637 643 652 667 667 667 681 759 766 775 783 797 816 849 851 888 902 908 919 919 926 926 954 981 1019 1019 1022 1026 1056 1082 1105 1110 1136 1140 1142 1142 1151 1152 1152 1166 1166 1166 1166 1172 1173 1173 1188 1194 1197 1212 1215 1229 1256 1280 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 +21 4 8 9 22 36 36 67 67 85 106 121 124 124 124 124 125 129 143 148 175 175 181 218 244 250 314 346 355 367 370 373 398 402 424 495 540 558 558 558 584 584 584 587 647 667 667 667 667 667 681 702 721 726 727 728 825 831 849 896 896 902 926 981 984 1026 1045 1056 1057 1058 1071 1074 1074 1104 1110 1126 1136 1140 1140 1143 1151 1152 1152 1152 1174 1197 1215 1238 1290 1291 1294 1307 1315 1315 1335 1335 1342 1343 1363 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 +22 9 9 22 30 37 67 67 67 67 67 67 86 99 121 124 134 142 143 164 164 168 170 170 172 172 175 175 217 220 235 236 237 245 307 310 328 355 355 362 362 373 394 412 412 458 464 558 589 667 667 681 704 721 766 775 785 791 880 896 906 958 974 984 1023 1023 1042 1045 1045 1058 1058 1071 1110 1122 1136 1143 1152 1173 1173 1215 1230 1291 1292 1296 1316 1331 1334 1334 1343 1343 1388 1390 1394 1396 1400 1419 1424 1426 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1443 1444 1445 1446 1447 1448 1448 1448 1449 1450 1451 1452 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 +23 9 32 64 67 67 95 99 118 124 129 181 182 234 234 307 307 327 355 362 362 362 413 558 643 658 658 667 667 704 721 766 857 1036 1056 1059 1136 1140 1152 1152 1173 1193 1230 1230 1267 1290 1292 1292 1331 1343 1355 1396 1476 1490 1491 1492 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 +24 4 10 18 22 23 27 32 34 36 45 45 47 51 51 53 61 61 122 131 157 187 188 196 210 218 220 235 286 286 312 312 315 316 327 367 373 394 394 394 404 429 429 430 446 476 476 520 552 585 621 647 668 720 726 726 748 769 781 783 785 785 793 793 840 884 923 986 1002 1007 1145 1171 1194 1207 1229 1274 1280 1322 1322 1375 1427 1448 1453 1492 1509 1510 1511 1512 1513 1514 1515 1516 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1544 1545 1546 1547 1548 1549 1550 1551 1552 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1561 1562 1563 1563 1564 1565 1566 1566 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 +25 3 5 5 15 23 30 32 32 36 40 52 86 133 182 182 188 196 197 210 217 218 218 218 233 235 238 286 286 307 314 350 350 362 372 373 394 404 404 429 430 457 477 504 513 520 608 620 664 785 785 785 791 793 795 795 798 808 808 823 922 940 940 954 960 1002 1002 1002 1026 1124 1124 1146 1153 1153 1171 1219 1219 1334 1334 1384 1394 1477 1478 1490 1509 1510 1511 1520 1529 1535 1540 1544 1546 1549 1549 1551 1555 1559 1559 1561 1562 1562 1562 1566 1566 1566 1566 1566 1566 1570 1571 1572 1573 1574 1574 1580 1584 1585 1588 1589 1601 1601 1601 1601 1604 1610 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1692 1693 1694 1695 1696 1697 1698 1699 1700 +26 23 23 34 45 45 53 61 61 77 97 131 157 161 164 164 164 168 182 182 210 215 219 234 252 310 343 345 362 373 394 400 427 429 457 476 481 496 504 504 506 506 513 520 520 539 620 621 621 643 725 726 726 726 728 766 780 791 793 795 795 862 862 931 960 1002 1027 1074 1097 1102 1177 1207 1217 1249 1313 1334 1334 1334 1334 1464 1465 1515 1520 1524 1535 1549 1559 1560 1561 1566 1566 1566 1566 1566 1569 1584 1594 1601 1624 1624 1629 1632 1633 1640 1649 1649 1658 1661 1661 1662 1672 1681 1693 1693 1693 1701 1702 1702 1703 1704 1704 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1733 1734 1735 1736 1737 1738 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 +27 22 34 40 45 53 85 118 129 164 168 172 217 217 228 234 286 307 347 372 372 373 398 423 426 427 428 429 456 465 504 513 520 554 587 621 663 726 793 854 954 960 1026 1146 1246 1334 1341 1341 1387 1465 1511 1515 1535 1540 1549 1566 1566 1595 1595 1624 1627 1630 1632 1633 1640 1661 1675 1675 1682 1688 1693 1693 1703 1716 1735 1755 1759 1765 1769 1775 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 +28 4 12 19 21 23 61 95 118 124 143 148 168 187 196 234 235 347 351 372 373 382 387 400 427 456 465 465 496 509 509 545 554 584 634 726 726 726 726 777 785 791 810 833 865 888 913 954 1026 1089 1097 1097 1146 1162 1217 1217 1229 1229 1238 1244 1322 1334 1334 1387 1387 1432 1477 1535 1549 1559 1566 1572 1574 1582 1583 1601 1635 1649 1657 1658 1669 1701 1701 1715 1716 1729 1730 1740 1744 1759 1773 1798 1799 1813 1815 1815 1828 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 +29 2 30 32 34 45 53 98 117 187 235 237 286 299 303 330 350 351 367 394 394 394 394 402 428 504 504 507 514 558 696 726 726 781 791 793 793 854 896 911 940 978 1007 1071 1146 1215 1302 1320 1334 1341 1364 1387 1465 1467 1510 1518 1521 1527 1535 1540 1546 1624 1645 1654 1654 1699 1701 1755 1760 1771 1788 1788 1794 1819 1850 1851 1857 1859 1861 1865 1867 1870 1891 1907 1948 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2013 2014 2015 2016 2017 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 +30 22 23 23 30 32 32 40 98 103 108 127 187 187 196 206 235 235 300 387 428 465 507 514 539 580 587 596 600 608 620 667 700 726 726 763 793 903 903 940 940 1010 1036 1225 1238 1282 1490 1540 1540 1597 1612 1676 1774 1794 1857 1859 1865 1865 1865 1900 1908 1940 1953 1957 1958 1959 1960 1968 1970 1972 1994 1994 2005 2012 2025 2045 2064 2065 2066 2067 2068 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2083 2084 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2107 2108 2109 2110 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2124 2125 2126 2127 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 +31 12 23 34 38 49 54 61 66 86 98 106 127 164 169 169 170 197 206 215 217 235 244 307 312 330 398 520 566 573 620 634 647 709 726 726 780 791 793 793 810 979 1004 1010 1023 1146 1173 1215 1334 1467 1478 1517 1528 1531 1546 1549 1566 1572 1640 1645 1693 1693 1693 1693 1734 1814 1852 1856 1859 1870 1952 1953 1953 1954 1958 1972 1972 2025 2046 2050 2065 2066 2067 2068 2070 2072 2074 2085 2097 2098 2110 2111 2142 2144 2149 2150 2161 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2258 2259 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 +32 15 19 23 29 32 32 34 36 36 61 61 77 81 83 86 97 124 129 130 131 143 168 169 169 197 202 206 210 235 250 286 291 310 325 340 347 367 372 373 380 394 394 399 427 496 496 558 593 634 723 726 726 748 785 793 793 843 902 933 940 984 1018 1146 1171 1229 1229 1229 1280 1313 1478 1478 1490 1510 1510 1517 1517 1520 1521 1539 1575 1640 1675 1693 1703 1715 1770 1783 1856 1859 1953 1957 1972 1976 2044 2046 2065 2072 2072 2089 2097 2102 2107 2125 2141 2141 2196 2198 2208 2208 2227 2229 2230 2230 2231 2250 2255 2281 2281 2282 2283 2284 2285 2286 2287 2288 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 +33 22 23 30 32 51 77 103 106 143 182 184 187 196 220 228 233 235 269 301 394 428 501 514 556 556 585 618 634 707 709 718 726 746 783 785 793 793 808 978 1004 1146 1146 1162 1228 1246 1387 1478 1545 1546 1597 1624 1693 1693 1714 1732 1734 1735 1783 1891 1914 1992 2046 2072 2072 2109 2134 2141 2152 2161 2161 2227 2235 2303 2307 2309 2336 2338 2346 2352 2353 2365 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 +34 7 22 22 23 32 32 100 106 157 182 196 196 196 211 211 218 218 239 346 357 372 394 416 423 428 428 444 456 470 470 496 496 525 584 618 634 658 660 725 726 726 764 777 785 791 833 841 891 941 978 1022 1124 1146 1197 1229 1266 1313 1387 1465 1466 1478 1490 1490 1527 1546 1549 1591 1624 1661 1688 1730 1836 1891 1901 1912 1967 1972 1992 1998 2165 2235 2253 2308 2310 2352 2385 2387 2394 2399 2405 2419 2420 2421 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 +35 13 23 23 29 30 32 52 65 100 100 103 103 106 106 121 125 143 157 187 211 252 285 310 314 367 384 397 398 400 412 423 423 444 470 470 520 554 584 596 596 597 620 668 696 709 718 726 785 791 808 888 888 908 952 953 970 981 981 981 1011 1026 1048 1123 1124 1143 1171 1246 1249 1302 1334 1334 1446 1447 1601 1604 1624 1640 1939 1939 1952 1958 1967 2070 2089 2141 2141 2352 2367 2379 2387 2396 2401 2424 2424 2432 2433 2434 2438 2439 2439 2440 2446 2448 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 +36 5 8 19 23 32 34 52 77 79 100 103 121 125 127 168 187 215 215 218 218 220 220 228 228 234 234 234 235 235 235 244 245 252 252 252 307 310 346 347 362 372 413 444 464 470 470 470 476 525 558 596 597 600 615 643 643 699 726 777 777 896 906 908 941 949 960 984 1060 1146 1146 1217 1249 1334 1341 1676 1814 1844 1852 1954 1992 2028 2059 2072 2115 2258 2288 2352 2387 2424 2432 2440 2464 2466 2467 2468 2470 2474 2485 2487 2495 2500 2501 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2557 2558 2559 2560 2561 +37 4 5 23 30 32 32 34 52 53 67 67 72 86 100 103 106 121 130 143 175 175 182 187 212 212 218 234 235 236 252 310 315 315 316 329 362 362 373 394 394 398 404 426 426 427 429 429 429 444 465 470 470 476 542 554 558 759 777 785 785 785 785 793 865 902 929 952 986 996 1074 1074 1097 1146 1217 1286 1302 1334 1334 1334 1387 1478 1510 1516 1618 1771 1783 1803 1939 1939 2046 2058 2331 2331 2387 2422 2424 2424 2466 2467 2474 2488 2495 2517 2519 2530 2530 2534 2534 2535 2536 2536 2539 2542 2546 2549 2557 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2594 2595 2596 2597 2598 2599 2600 2601 +38 106 106 141 239 344 370 496 520 558 680 840 848 848 857 958 958 974 984 1019 1022 1060 1071 1152 1159 1375 1375 1510 1543 1545 1545 1545 1590 1783 2209 2242 2521 2565 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2616 2617 2618 2619 2620 2621 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 +39 8 9 21 21 23 27 30 32 36 46 47 48 53 62 62 64 67 67 67 67 68 68 70 70 71 71 77 81 85 86 86 86 87 87 88 89 90 91 95 97 97 97 98 99 99 100 101 103 107 108 109 113 125 125 125 143 157 161 168 169 170 175 182 182 186 189 197 197 199 208 210 213 213 219 228 234 241 308 357 372 394 404 429 520 543 702 744 766 766 766 766 777 793 880 880 884 887 933 933 976 1334 1334 1334 1374 1375 1432 1515 1727 1774 1958 2141 2272 2350 2426 2432 2477 2544 2562 2572 2575 2648 2649 2650 2651 2652 2653 2654 2655 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2677 2678 2679 2680 2681 2681 2682 2683 2684 2685 2686 2687 2688 2688 2689 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 +40 8 8 12 17 19 23 23 23 24 26 27 28 30 31 32 34 38 39 42 45 46 49 51 52 53 53 53 54 62 62 65 66 68 69 69 70 71 73 76 77 81 86 87 88 90 90 91 92 93 94 95 96 98 99 100 100 106 107 108 109 110 112 113 114 125 125 143 169 169 170 175 175 181 184 192 199 207 208 208 213 219 228 235 255 258 342 347 358 398 539 558 558 585 592 681 721 744 759 785 790 880 896 898 960 1074 1074 1334 1334 1334 1478 1510 1510 1618 1619 1619 1643 1649 2046 2181 2182 2208 2218 2419 2426 2594 2675 2678 2689 2704 2707 2713 2715 2716 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 +41 7 8 15 16 18 19 21 21 21 21 23 23 24 26 27 32 32 34 36 39 45 46 47 60 62 64 68 69 76 79 80 81 86 87 95 107 109 113 125 125 133 148 152 156 157 159 161 163 164 170 173 177 181 182 182 183 187 187 190 191 192 193 196 197 198 198 198 199 199 202 203 206 208 212 214 216 231 234 239 241 247 252 255 346 347 355 399 486 542 766 769 769 777 791 809 880 896 902 957 1059 1059 1060 1123 1124 1152 1192 1221 1334 1334 1446 1462 1783 1814 2027 2281 2281 2311 2313 2315 2319 2429 2433 2479 2484 2571 2571 2592 2674 2713 2715 2729 2731 2759 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 +42 8 9 15 18 19 21 23 24 62 64 64 67 67 68 79 81 85 118 124 124 125 125 127 127 129 131 131 133 134 134 138 144 159 161 163 175 188 190 196 208 219 219 225 228 234 234 237 241 244 247 252 255 258 261 310 310 310 310 310 310 316 316 344 350 355 355 362 362 362 554 558 681 681 685 721 744 760 760 766 849 880 887 896 896 896 896 896 901 984 1026 1060 1097 1238 1334 1334 1665 2043 2044 2442 2442 2612 2653 2673 2674 2779 2796 2807 2822 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2842 2843 2844 2845 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 +43 8 8 22 24 67 85 122 125 133 140 140 143 159 161 186 197 210 218 221 223 227 234 234 235 237 241 243 244 244 256 266 278 278 283 285 291 301 302 302 303 305 305 308 308 310 314 315 315 334 335 337 339 339 340 340 342 344 344 346 347 350 350 351 351 358 358 362 363 372 394 402 456 458 664 713 721 733 766 808 826 880 896 919 919 919 933 954 968 987 1026 1036 1060 1060 1074 1097 1118 1151 1192 1228 1244 1334 1387 1394 1465 1478 1624 1649 1684 1771 1884 1936 2046 2058 2228 2421 2442 2442 2484 2532 2562 2576 2815 2817 2835 2835 2836 2848 2848 2862 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2877 2878 2879 2880 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 +44 +45 23 30 32 32 53 61 67 74 77 108 122 129 164 175 181 184 218 218 218 327 346 355 357 371 373 373 373 373 373 373 375 377 380 380 381 384 385 389 393 393 396 397 400 404 404 405 407 409 410 412 414 414 416 417 418 419 420 428 429 430 438 439 442 444 445 446 447 450 450 453 456 456 462 465 466 467 474 480 492 495 498 503 512 513 516 519 528 529 535 552 558 565 584 593 595 642 646 649 667 668 680 722 741 741 741 854 922 954 1071 1097 1146 1334 1387 1465 1465 1479 1549 1624 2141 2208 2396 2396 2416 2442 2683 2773 2920 2934 2935 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 +46 9 143 217 218 218 234 234 234 234 269 285 307 311 346 372 372 382 393 393 395 396 412 412 418 430 434 441 442 442 442 443 445 454 456 456 467 489 493 498 498 501 503 506 507 511 511 515 516 517 518 524 526 531 534 539 542 546 554 556 558 564 565 566 567 569 571 581 582 585 586 588 589 589 590 590 599 605 606 613 613 614 627 639 696 744 744 745 755 782 791 791 791 840 845 860 926 974 1022 1022 1029 1071 1097 1146 1217 1372 1401 1501 1788 1788 1936 1936 2010 2350 2421 2429 2442 2442 2545 2609 2803 2894 2952 2975 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 +47 7 30 36 64 67 67 67 97 122 124 126 127 170 175 181 181 183 184 184 187 187 187 187 187 190 221 231 234 307 310 346 355 355 355 362 362 372 373 375 377 387 393 394 398 416 429 439 439 441 442 448 457 467 498 504 511 520 551 555 557 558 558 558 558 558 558 558 559 561 562 564 565 567 572 576 577 584 585 593 602 610 619 620 621 625 637 638 639 643 645 646 646 647 649 651 654 656 658 658 658 658 660 665 666 668 674 675 678 682 682 687 687 689 691 691 693 693 694 698 718 721 722 725 728 745 755 772 785 790 790 791 793 809 826 849 851 906 919 926 954 958 974 998 1060 1074 1074 1074 1074 1206 1269 1334 1334 1375 1384 1425 1492 1521 1545 1624 1765 1935 2081 2141 2141 2292 2442 2442 2484 2484 2484 2484 2673 2766 2800 2821 2891 2995 3008 3016 3017 3018 3019 3020 3021 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 +48 10 32 32 67 97 124 157 196 208 210 220 234 244 307 308 310 310 310 315 328 346 355 355 358 362 362 362 362 374 384 387 387 393 394 394 404 410 416 418 423 423 440 441 442 467 495 510 516 551 572 572 582 584 585 589 593 616 619 620 621 626 628 634 636 637 637 640 640 644 646 646 651 653 653 654 656 658 658 660 668 669 672 674 675 675 678 682 687 687 689 689 691 696 703 709 710 716 717 720 723 726 727 728 731 733 734 736 741 741 742 743 746 746 748 755 755 785 809 809 828 861 909 913 1060 1060 1387 1389 1539 1662 1814 2081 2081 2141 2432 2557 2562 2571 2612 2812 2812 2930 2958 2970 3031 3034 3051 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 +49 9 30 30 30 32 32 32 53 53 67 97 125 127 157 181 181 181 218 231 234 234 327 327 344 346 346 347 362 362 362 396 397 404 410 414 495 500 533 539 551 558 572 572 589 620 626 627 636 644 646 656 662 662 662 682 696 697 702 703 709 709 710 714 717 718 722 725 726 727 729 730 731 732 734 735 736 741 741 743 746 746 747 750 752 752 760 763 770 772 774 775 776 777 780 781 787 790 795 807 809 809 811 817 819 821 854 863 865 879 880 906 924 940 954 1060 1097 1097 1097 1097 1166 1539 1539 1624 1661 1661 1661 1693 1814 1814 1958 1992 2081 2141 2141 2421 2428 2489 2554 2891 3023 3054 3073 3075 3077 3081 3083 3089 3090 3090 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 +50 9 9 9 9 9 23 23 30 51 61 61 67 129 175 175 181 210 210 219 234 237 286 308 308 308 308 308 308 346 347 362 362 362 373 378 387 389 412 412 412 412 441 441 486 495 498 509 552 585 596 607 637 646 649 651 657 680 683 685 699 709 716 716 717 717 718 726 735 741 746 757 758 759 762 763 764 766 766 766 773 775 775 777 781 781 791 791 797 804 807 808 808 809 810 812 824 828 830 844 851 852 854 857 857 858 858 861 865 866 869 871 874 880 880 896 907 911 913 916 931 931 954 958 974 1097 1215 1334 1334 1389 1462 1471 1471 1479 1624 1668 1814 1814 1952 1953 1992 2046 2072 2115 2141 2477 2502 2545 2550 2596 2672 2786 2796 2804 2921 2992 2992 2992 3039 3041 3105 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3137 3138 3139 3140 3141 3142 3143 3144 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 +51 9 23 30 30 32 32 61 64 83 86 98 124 124 124 125 172 181 181 187 218 220 244 245 305 305 308 308 308 308 308 315 340 356 423 429 441 456 457 465 495 498 498 558 558 558 615 621 637 643 643 643 647 651 658 668 668 668 681 702 717 721 760 763 766 769 785 785 785 787 791 808 808 809 810 816 826 828 829 830 831 833 836 842 843 848 849 857 860 863 866 866 878 880 886 896 898 906 908 908 908 909 911 911 913 916 916 924 933 933 933 933 933 933 933 935 942 958 958 960 984 1004 1011 1011 1058 1060 1071 1146 1146 1166 1189 1217 1219 1310 1334 1350 1425 1462 1478 1515 1604 1832 1847 1939 2027 2133 2426 2536 2567 2567 2707 2926 2947 2947 2982 3041 3109 3115 3124 3126 3128 3130 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 +52 5 23 34 37 98 105 105 106 127 131 157 175 182 182 206 210 210 217 218 218 218 231 244 308 308 308 308 329 389 394 423 441 448 498 498 584 597 637 637 668 675 760 762 777 783 785 785 793 808 808 854 854 862 880 896 896 898 902 902 903 906 906 908 908 910 911 911 912 913 916 919 922 923 931 933 933 934 936 940 942 944 946 949 984 992 995 996 1002 1004 1004 1011 1014 1058 1058 1071 1074 1074 1146 1194 1309 1313 1334 1471 1485 1519 1649 1715 1939 2419 2477 2484 2664 2766 2815 2993 3129 3168 3171 3173 3173 3178 3181 3183 3198 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 +53 18 30 32 61 67 67 67 124 124 127 127 133 168 169 175 181 197 217 218 218 231 235 247 252 266 286 286 347 373 394 394 394 394 394 394 394 441 441 441 465 476 476 552 559 561 584 584 585 643 643 647 647 647 777 790 790 790 791 791 797 832 842 896 908 916 944 955 956 960 973 973 974 974 981 981 987 987 992 995 996 999 1002 1006 1010 1011 1014 1027 1028 1029 1030 1033 1034 1034 1056 1058 1058 1058 1071 1071 1074 1097 1166 1166 1194 1219 1302 1334 1387 1479 1539 1589 1619 1624 1693 1709 1814 1844 2058 2085 2301 2664 2700 2839 2949 2975 2999 3104 3168 3212 3213 3214 3214 3214 3216 3223 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 +54 9 9 27 67 67 67 67 125 127 157 168 172 172 230 231 241 244 312 312 347 362 373 413 441 442 498 558 558 558 584 613 613 637 667 721 741 785 785 880 896 896 919 981 1002 1018 1019 1027 1030 1034 1040 1058 1058 1068 1071 1074 1074 1085 1174 1228 1347 1542 2045 2421 2865 3159 3212 3220 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 +55 9 9 9 9 9 9 9 9 9 9 23 30 64 67 67 67 67 67 118 125 125 125 125 172 172 172 172 172 172 175 175 175 175 181 181 181 182 199 218 234 234 234 234 234 235 235 235 235 235 235 235 235 239 307 307 343 346 346 346 355 355 355 355 355 355 362 362 362 372 373 394 394 394 394 413 517 558 558 558 558 558 584 587 637 658 667 667 667 721 721 721 721 766 785 785 842 880 958 958 974 974 974 981 1018 1023 1023 1035 1035 1035 1037 1045 1052 1053 1054 1058 1059 1059 1060 1062 1066 1066 1067 1067 1071 1073 1074 1074 1074 1074 1074 1074 1074 1074 1074 1074 1074 1074 1074 1074 1085 1089 1166 1334 1347 1389 1389 1462 1490 1619 2350 2421 2429 2527 2638 2796 2796 2865 3097 3134 3214 3223 3223 3223 3253 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 +56 7 9 9 21 30 32 32 36 64 67 67 67 67 67 67 67 67 67 67 67 67 67 67 125 125 125 125 125 125 125 125 129 129 129 161 172 172 172 172 172 172 172 175 175 175 175 175 175 181 181 181 181 181 181 181 181 190 219 234 234 234 234 234 234 239 328 346 346 355 373 373 412 558 558 558 558 558 558 558 558 584 584 658 658 721 721 721 721 721 721 721 722 880 880 1023 1037 1037 1040 1058 1058 1058 1059 1060 1060 1068 1074 1074 1074 1074 1074 1074 1074 1080 1082 1085 1086 1089 1097 1097 1097 1098 1217 1228 1334 1334 1401 1478 1479 1479 1492 2396 2562 2571 2796 2949 3149 3267 3280 3289 3295 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 +57 9 9 9 30 32 62 67 67 67 67 67 67 67 67 67 67 67 125 125 129 161 164 164 168 172 172 181 234 310 328 328 346 355 355 362 362 362 373 373 373 373 412 558 558 558 558 584 584 658 721 721 721 766 766 766 840 880 880 880 933 933 958 1060 1074 1074 1074 1074 1074 1074 1074 1074 1074 1074 1074 1082 1085 1093 1097 1334 1334 1492 1732 2027 2041 3315 3315 3315 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3340 3341 +58 3 3 3 3 9 23 40 53 64 103 120 124 129 129 129 175 175 175 175 175 181 234 280 328 353 355 373 373 373 373 373 373 373 402 419 439 456 486 506 506 520 539 558 558 587 587 653 658 668 680 741 766 789 825 857 908 956 974 996 1023 1036 1036 1097 1134 1139 1142 1143 1156 1166 1178 1178 1181 1213 1217 1226 1234 1246 1271 1274 1334 1335 1479 1510 1604 1624 1704 1774 1958 2072 2315 2350 2421 2442 2554 2590 2742 2742 3097 3115 3173 3336 3338 3338 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3403 3404 3405 3406 3407 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 +59 22 30 30 40 53 63 67 67 67 83 85 99 124 124 124 127 127 134 168 172 175 175 181 197 244 245 286 291 308 308 314 315 315 338 347 355 362 367 394 413 540 558 572 597 643 667 667 667 680 725 766 829 884 887 896 906 908 911 912 919 922 981 984 1007 1058 1071 1097 1105 1107 1110 1112 1138 1140 1142 1145 1146 1146 1146 1158 1166 1168 1170 1170 1171 1172 1173 1181 1181 1181 1182 1182 1182 1184 1184 1185 1186 1194 1194 1219 1228 1245 1256 1262 1263 1264 1265 1269 1274 1278 1292 1324 1334 1334 1335 1335 1342 1363 1424 1426 1478 1486 1506 1506 1519 1574 1591 1600 1649 1770 1939 2043 2151 2208 2419 2442 2442 2545 2562 2575 2674 2715 2742 3092 3103 3173 3178 3307 3315 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3461 3462 3463 3464 3465 3466 3467 3468 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 +60 34 64 67 97 99 118 124 124 125 127 168 172 175 182 187 199 208 218 244 308 308 308 362 558 558 558 587 652 667 667 686 721 726 753 769 782 785 785 896 896 908 908 911 926 981 986 1018 1045 1056 1097 1110 1137 1140 1142 1142 1142 1145 1146 1146 1146 1146 1151 1152 1152 1152 1152 1166 1166 1166 1168 1180 1182 1188 1197 1208 1215 1225 1256 1256 1257 1284 1285 1286 1289 1290 1293 1294 1297 1298 1302 1302 1303 1305 1306 1308 1309 1311 1312 1315 1316 1317 1320 1321 1323 1324 1324 1325 1326 1327 1328 1329 1330 1334 1334 1335 1338 1339 1341 1343 1349 1350 1350 1351 1358 1359 1363 1401 1431 1432 1478 1486 1506 1591 1636 1762 1783 1934 1963 2070 2208 2419 2421 2701 2726 2772 2774 2796 2835 3166 3212 3213 3233 3336 3338 3441 3458 3485 3486 3487 3488 3489 3490 3491 3492 3493 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 +61 22 22 24 67 67 67 99 118 124 124 127 134 168 168 175 234 241 244 314 327 327 344 346 346 350 355 355 362 499 558 558 584 585 587 646 647 667 768 896 896 902 919 919 919 926 974 981 1019 1026 1045 1045 1056 1056 1110 1142 1142 1158 1158 1166 1197 1204 1230 1315 1319 1335 1335 1342 1343 1347 1368 1372 1375 1376 1380 1381 1390 1392 1394 1395 1398 1401 1402 1409 1413 1420 1421 1422 1424 1426 1432 1486 1487 1507 1508 1521 1575 1636 1783 2367 2426 2664 2674 2796 2828 2994 3097 3166 3315 3315 3336 3391 3447 3512 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 +62 22 30 32 65 67 67 85 86 98 170 175 246 248 327 355 362 362 558 643 643 643 652 667 667 667 702 906 919 922 930 958 1097 1097 1110 1136 1140 1152 1152 1173 1173 1174 1292 1292 1316 1343 1390 1396 1400 1419 1424 1433 1436 1437 1437 1438 1440 1442 1448 1452 1453 1454 1458 1463 1465 1467 1467 1477 1477 1478 1479 1485 1489 1493 1500 1506 2399 2674 2975 3253 3281 3355 3530 3531 3552 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 +63 32 32 67 122 126 172 172 175 175 234 235 257 286 307 314 355 362 362 373 412 412 558 558 558 558 585 585 643 721 857 1026 1059 1097 1136 1152 1229 1334 1396 1426 1489 1495 1497 1500 1501 1936 2421 2527 2608 2911 3028 3097 3336 3531 3531 3558 3569 3574 3582 3583 3584 3585 3586 3587 3588 3589 3589 3590 3591 3592 3593 3594 +64 13 45 61 122 124 196 208 237 394 395 404 404 430 453 458 470 471 476 476 600 679 741 748 783 809 1002 1010 1023 1153 1166 1217 1229 1510 1511 1517 1519 1520 1525 1535 1539 1539 1542 1542 1544 1544 1546 1547 1548 1549 1551 1552 1554 1555 1559 1560 1562 1562 1562 1566 1566 1572 1574 1576 1579 1580 1580 1586 1588 1589 1591 1593 1594 1595 1595 1597 1598 1601 1603 1604 1605 1607 1608 1610 1616 1617 1633 1638 1643 1649 1652 1658 1669 1685 1695 1700 1701 1728 1735 1759 1759 1780 1817 1907 1990 1997 2006 2040 2065 2109 2198 2326 2333 2333 2396 2424 2432 2502 2557 2557 2594 2674 2832 3028 3036 3066 3075 3106 3145 3166 3214 3218 3256 3342 3342 3389 3389 3462 3531 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 +65 5 32 34 52 53 98 124 131 143 157 182 182 196 196 206 210 218 235 238 305 350 370 384 384 387 397 404 413 427 446 457 465 465 486 496 504 506 533 540 585 600 620 621 634 634 658 711 725 726 728 777 795 822 825 922 922 940 1002 1146 1153 1229 1229 1322 1334 1334 1341 1341 1432 1517 1531 1535 1535 1544 1559 1561 1562 1566 1566 1566 1566 1571 1572 1577 1580 1601 1604 1614 1619 1621 1623 1629 1634 1637 1639 1640 1643 1649 1649 1650 1658 1662 1663 1669 1677 1680 1680 1683 1684 1686 1687 1688 1690 1691 1693 1693 1696 1698 1700 1702 1707 1708 1735 1757 1759 1760 1768 1770 1771 1852 1898 1904 1908 1919 1939 1952 2045 2072 2097 2141 2182 2208 2311 2350 2369 2429 2550 2664 2774 2823 3006 3062 3109 3186 3247 3256 3342 3614 3627 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 +66 22 23 30 32 34 36 61 129 129 161 164 175 187 218 234 252 267 286 299 307 327 344 398 436 456 457 457 471 477 477 481 620 621 643 718 726 728 759 763 785 791 791 793 797 850 901 913 1074 1124 1124 1146 1177 1189 1217 1229 1274 1334 1334 1334 1375 1478 1535 1549 1557 1566 1566 1566 1595 1629 1639 1649 1649 1654 1658 1661 1662 1675 1681 1693 1693 1698 1702 1711 1714 1716 1727 1727 1727 1728 1730 1732 1732 1736 1740 1742 1744 1757 1759 1759 1760 1762 1762 1762 1765 1768 1769 1780 1814 1816 1827 1837 1840 1840 1841 1901 1904 1907 1912 1921 1948 1952 1952 1989 2040 2065 2083 2166 2208 2250 2385 2674 2823 2823 3196 3342 3616 3657 3660 3663 3670 3685 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3739 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 +67 23 32 53 67 67 143 168 182 182 196 237 307 347 355 374 394 413 428 456 465 496 537 584 663 709 725 725 725 726 726 726 833 833 863 865 974 1026 1026 1074 1102 1215 1215 1229 1384 1387 1387 1478 1518 1540 1605 1621 1624 1624 1633 1654 1654 1658 1661 1682 1693 1716 1729 1755 1759 1798 1838 1844 1847 1850 1851 1852 1857 1859 1863 1882 1891 1893 1902 1903 1914 1922 1927 1928 1937 1938 1948 1953 1958 1958 2046 2088 2098 2172 2250 2288 2331 2350 2352 2352 2367 2378 2459 2557 2560 2574 2664 2835 3164 3166 3196 3233 3342 3579 3633 3656 3699 3735 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 +68 4 5 17 23 30 32 77 98 122 182 355 372 400 400 448 465 496 584 600 725 777 793 831 843 854 902 978 1002 1020 1146 1146 1217 1217 1367 1387 1432 1432 1465 1535 1549 1566 1583 1624 1633 1649 1654 1669 1716 1735 1755 1759 1783 1846 1851 1857 1859 1861 1865 1872 1872 1881 1881 1898 1901 1907 1908 1922 1925 1932 1939 1946 1948 1953 1954 1957 1959 1962 1962 1963 1964 1965 1968 1972 1974 1987 1988 1990 1991 1992 1995 1997 2008 2009 2010 2011 2013 2025 2028 2032 2034 2055 2061 2062 2072 2109 2141 2150 2209 2250 2250 2350 2433 2473 2545 2562 2564 2704 2726 2749 2828 2863 2890 2914 2949 3000 3075 3092 3133 3213 3282 3362 3389 3618 3720 3734 3735 3761 3762 3766 3780 3801 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 +69 8 23 27 30 74 81 121 126 129 173 187 192 241 347 394 396 419 426 438 446 496 587 596 643 647 696 720 726 726 763 783 785 791 869 903 962 965 978 1238 1341 1510 1527 1543 1546 1633 1633 1649 1650 1671 1673 1765 1788 1857 1859 1865 1870 1900 1912 1942 1948 1952 1953 1954 1990 2007 2011 2012 2019 2028 2028 2061 2064 2070 2071 2074 2077 2081 2082 2083 2109 2114 2136 2145 2146 2154 2161 2171 2172 2219 2250 2383 2396 2432 2535 2674 2828 2828 3135 3154 3342 3558 3665 3720 3735 3793 3818 3827 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 +70 40 62 67 98 127 129 173 175 234 245 398 429 429 458 496 514 539 585 585 620 643 726 781 785 789 793 793 793 979 1060 1153 1238 1334 1401 1478 1478 1490 1510 1535 1579 1675 1676 1730 1752 1814 1859 1870 1880 1904 1914 1958 1959 2006 2025 2037 2044 2067 2070 2081 2097 2098 2109 2111 2142 2181 2183 2195 2208 2233 2241 2243 2245 2249 2250 2261 2263 2265 2266 2268 2271 2272 2276 2333 2350 2353 2365 2374 2528 2616 2664 2683 2738 2817 2836 2890 3003 3047 3092 3106 3161 3207 3207 3500 3610 3611 3614 3618 3656 3720 3761 3780 3793 3832 3950 3950 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 +71 13 29 30 30 32 77 98 121 124 127 129 151 169 202 206 220 234 314 387 395 539 554 585 585 720 723 744 748 793 940 954 978 979 979 1002 1010 1023 1425 1539 1540 1549 1661 1661 1665 1675 1696 1715 1770 1772 1859 2025 2050 2097 2109 2141 2149 2194 2196 2227 2230 2230 2253 2303 2326 2334 2334 2342 2349 2350 2365 2366 2366 2367 2367 2367 2369 2396 2399 2411 2459 2492 2550 2583 2653 2776 2816 2863 2993 3028 3124 3217 3359 3389 3445 3610 3610 3656 3720 3725 3782 3881 3889 3937 3937 3973 4013 4035 4056 4064 4065 4066 4067 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4077 4078 4079 4080 4081 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 +72 21 23 23 27 32 48 53 77 129 129 143 169 172 188 196 234 235 235 291 392 397 429 429 471 486 580 615 620 726 726 777 785 790 791 791 793 810 843 1027 1146 1215 1334 1478 1539 1601 1682 1703 1715 1715 1730 1732 1749 1783 1860 1891 1912 1968 1968 1972 2044 2046 2072 2101 2134 2141 2141 2167 2171 2196 2198 2208 2227 2230 2235 2250 2253 2299 2299 2303 2308 2322 2327 2338 2352 2361 2365 2369 2373 2390 2391 2392 2396 2397 2398 2401 2404 2408 2411 2411 2424 2444 2545 2550 2564 2673 2817 2864 3115 3207 3256 3589 3618 3710 3720 3761 3889 3894 3915 3928 4029 4035 4082 4110 4150 4151 4152 4153 4154 4155 4156 4157 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 +73 7 18 23 30 32 61 103 103 118 124 169 196 218 235 286 307 314 370 394 423 428 444 456 572 572 660 707 709 726 726 793 793 863 865 978 1022 1029 1054 1124 1229 1229 1274 1334 1447 1478 1510 1539 1539 1545 1619 1740 1783 1891 1901 1914 1939 1939 1968 2018 2040 2046 2101 2281 2310 2352 2394 2409 2424 2429 2430 2432 2433 2434 2436 2450 2457 2459 2486 2492 2503 2537 2930 2984 3256 3269 3276 3510 3558 3614 4166 4190 4204 4204 4205 4206 4207 4208 4209 4210 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 +74 22 29 32 53 103 182 196 237 402 423 423 428 444 456 456 496 513 525 574 597 618 660 660 726 763 793 793 808 872 924 954 1074 1137 1146 1229 1249 1334 1447 1582 1608 1619 1649 1952 1954 1958 1964 1965 1967 2046 2046 2101 2387 2396 2397 2424 2429 2429 2430 2431 2432 2434 2435 2439 2450 2458 2474 2503 2508 2509 2531 2543 2555 2590 2618 2745 2853 2890 3445 3545 3928 4164 4212 4213 4214 4220 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 +75 13 18 22 22 23 30 32 32 32 34 36 67 103 106 168 196 218 219 220 234 237 247 252 252 307 444 465 470 520 539 765 791 793 872 896 919 924 929 954 978 1137 1146 1334 1334 1334 1334 1341 1341 1387 1478 1478 1517 1539 1640 1693 1912 1954 1992 2058 2141 2235 2350 2365 2383 2387 2442 2466 2467 2471 2474 2482 2483 2486 2490 2492 2495 2519 2526 2528 2528 2528 2529 2534 2536 2537 2539 2541 2545 2546 2549 2552 2553 2555 2556 2557 2557 2558 2560 2562 2586 2590 2597 2934 3089 3187 3209 3499 3531 3614 3618 3828 4026 4039 4177 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 +76 4 5 5 8 15 22 27 32 61 61 77 100 106 125 131 157 182 197 220 234 236 252 252 293 310 357 362 369 394 394 426 426 426 427 429 444 456 540 584 668 699 721 766 791 854 861 865 895 906 940 973 1074 1124 1146 1166 1217 1217 1217 1280 1349 1478 1478 1510 1516 1582 1618 1624 2028 2046 2387 2423 2424 2424 2433 2467 2470 2485 2488 2519 2530 2531 2534 2534 2536 2536 2539 2542 2549 2550 2557 2557 2562 2562 2566 2569 2574 2576 2577 2578 2581 2587 2588 2590 2590 2590 2594 2594 2595 2597 2598 2599 2600 2601 2654 2664 2766 2815 2865 2865 2907 2934 3006 3070 3099 3115 3149 3618 3801 4260 4281 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 +77 24 27 28 30 61 72 106 124 125 125 125 125 125 129 187 210 218 220 230 234 235 244 247 264 307 308 308 310 340 362 373 394 394 396 429 456 499 558 587 613 642 658 681 766 780 785 785 793 797 851 865 880 896 896 906 908 919 960 1002 1097 1097 1166 1194 1207 1267 1334 1334 1334 1334 1341 1341 1515 1601 1604 1624 1678 1687 1688 1700 1713 1844 1868 1952 2141 2182 2367 2385 2396 2422 2422 2424 2424 2424 2466 2467 2470 2484 2497 2517 2528 2534 2536 2541 2542 2566 2567 2568 2568 2569 2570 2571 2572 2572 2577 2578 2580 2581 2586 2589 2590 2590 2590 2593 2599 2599 2600 2664 2664 2741 2875 3066 3070 3159 3214 3256 3282 3289 3315 3315 3600 3889 3913 4292 4301 4305 4307 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 +78 27 129 155 196 221 231 303 307 394 396 397 476 496 556 584 613 650 658 680 720 720 880 908 908 908 940 954 962 1029 1060 1074 1097 1209 1215 1315 1334 1334 1462 1762 2141 2338 2424 2466 2470 2470 2517 2536 2572 2577 2578 2580 2590 2631 2673 2864 2865 2907 3090 3166 3207 3256 3338 3338 3829 4220 4227 4265 4265 4271 4298 4305 4305 4338 4338 4339 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 +79 9 9 21 21 21 23 23 32 36 45 47 63 67 67 67 67 67 67 67 67 67 67 68 70 71 74 84 85 86 87 88 90 91 97 98 99 100 103 103 113 130 157 161 170 175 175 175 175 182 190 196 197 199 208 213 213 235 235 343 357 394 420 429 558 558 766 769 791 791 848 880 906 922 958 978 1059 1146 1146 1173 1194 1215 1215 1215 1215 1282 1479 1479 1619 1814 1914 1958 2027 2141 2141 2259 2287 2287 2432 2465 2544 2650 2652 2655 2659 2661 2668 2669 2672 2673 2675 2677 2678 2681 2681 2683 2696 2696 2704 2713 2713 2717 2744 2748 2760 2761 2796 2803 2812 2849 2870 3020 3020 3315 3336 3336 3338 3638 3832 4279 4363 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4452 4453 +80 21 23 24 27 28 32 33 39 46 53 53 62 62 62 63 64 66 67 68 69 71 71 73 74 79 81 83 84 85 86 87 88 95 99 99 100 103 106 107 108 113 124 124 124 125 172 181 186 196 197 201 208 213 235 241 258 340 394 558 589 643 681 681 681 760 766 793 888 1023 1056 1057 1102 1153 1292 1373 1478 1478 1478 1478 1506 1510 1515 1527 1601 1636 1676 1844 2141 2287 2357 2496 2534 2571 2575 2653 2658 2665 2668 2669 2671 2672 2675 2677 2678 2688 2688 2696 2703 2707 2716 2731 2740 2744 2753 2760 2760 2763 2764 2766 2767 2776 2780 2796 2801 2807 2821 2843 2851 2852 2861 2886 2900 3150 3173 3217 3336 3338 3529 3614 3766 4323 4363 4398 4404 4410 4410 4417 4422 4432 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4473 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 +81 8 8 9 16 21 23 24 27 28 30 32 38 39 40 43 45 46 49 51 54 55 62 65 66 68 69 70 74 76 80 81 85 86 87 88 88 88 94 95 98 99 99 100 106 109 109 110 113 137 143 169 182 182 187 188 190 190 192 196 196 203 208 217 219 231 234 255 286 308 347 558 631 766 902 902 1123 1244 1334 1387 1450 1992 2046 2209 2238 2426 2651 2674 2678 2713 2713 2713 2735 2738 2748 2757 2760 2760 2763 2800 2801 2807 2808 2853 2855 2861 2886 2900 3207 3256 3624 3713 4216 4363 4405 4410 4484 4484 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 +82 9 24 27 37 39 40 45 46 64 64 67 67 68 68 69 69 79 79 81 85 87 95 109 113 115 117 122 124 127 135 135 139 142 143 145 157 159 161 168 181 188 188 191 192 196 197 208 208 211 213 219 239 241 247 256 257 258 307 310 343 558 587 643 760 896 960 1019 1023 1074 1197 1207 1292 1401 1403 1587 2151 2419 2571 2623 2654 2664 2674 2674 2674 2777 2780 2796 2808 2835 2844 2844 2851 2855 2862 2900 3036 3143 3238 3238 3315 3531 3531 3766 3803 4296 4405 4462 4502 4506 4509 4509 4516 4523 4533 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 diff --git a/Matlab/Tests/README.txt b/Matlab/Tests/README.txt new file mode 100644 index 0000000000..e1aed9ed39 --- /dev/null +++ b/Matlab/Tests/README.txt @@ -0,0 +1 @@ +This directory contains the same tests than the ones used in the c++ library corelib. \ No newline at end of file diff --git a/Matlab/Tests/TestAdjustLikelihood.m b/Matlab/Tests/TestAdjustLikelihood.m new file mode 100644 index 0000000000..50a07fedcc --- /dev/null +++ b/Matlab/Tests/TestAdjustLikelihood.m @@ -0,0 +1,10 @@ +close all +clear all + +% Test AvpdCore::BayesFilter::adjustLikelihood() +likelihood = [0 0 0 0]; +adjustLikelihoodResult1 = adjustLikelihood(likelihood) +likelihood = [0.3 0.4 0.2 0.9]; +theMean = mean(likelihood) +theSdtDev = std(likelihood) +adjustLikelihoodResult2 = floor(adjustLikelihood(likelihood)*1000) \ No newline at end of file diff --git a/Matlab/Tests/TestBayesFilter.m b/Matlab/Tests/TestBayesFilter.m new file mode 100644 index 0000000000..10a8fe58f8 --- /dev/null +++ b/Matlab/Tests/TestBayesFilter.m @@ -0,0 +1,35 @@ +close all +clear all + +% Test AvpdCore::BayesFilter::computePosterior() +prior = 1; +predictionNP = 0.9; +predictionLC = [0 0.22 0.19 0.25 0.04 0.1 0.02 0.04 0.01 0.01]; +likelihood = []; +nIter = 10; + +computePosteriorResult = zeros(nIter); +for i=1:nIter + likelihood = [likelihood; 1]; + prediction = generatePrediction(predictionNP, predictionLC, length(likelihood)-1) + if i==1 + prior = 1; + %elseif i>2 + % prior = [prior;prior(end)]; %use the same probability as the previous neighbor + else + prior = [prior;0]; + end + prior = likelihood .* (prediction' * prior); + prior = prior/sum(prior); %Normalize + computePosteriorResult(1:length(prior),i) = prior; +end + +%Adjust results (don't use float to compare) +computePosteriorResult = floor(computePosteriorResult*1000)'; +disp('computePosteriorResult='); +disp(computePosteriorResult); +r = sum(sum((computePosteriorResult - [1000,0,0,0,0,0,0,0,0,0;900,99,0,0,0,0,0,0,0,0;810,113,75,0,0,0,0,0,0,0;729,109,96,64,0,0,0,0,0,0;656,100,98,87,56,0,0,0,0,0;590,93,95,93,78,49,0,0,0,0;531,86,90,92,85,69,42,0,0,0;478,80,86,90,86,77,62,37,0,0;430,75,81,87,86,80,70,55,33,0;387,69,77,83,84,80,73,63,49,29;]) ~= 0)); +if r ~= 0 + error('computePosteriorResult is not valid!') +end + diff --git a/Matlab/Tests/TestComputeLikelihood.m b/Matlab/Tests/TestComputeLikelihood.m new file mode 100644 index 0000000000..7c36a14171 --- /dev/null +++ b/Matlab/Tests/TestComputeLikelihood.m @@ -0,0 +1,28 @@ +close all +clear all + +% Test AvpdCore::KeypointMemory::getLikelihood() + +% load data +memory = dlmread('090306-3_db-Signatures.txt', ' ', 1, 0); +dictionary = dlmread('090306-3_db-Dictionary.txt', ' ', 1, 0); + +%update the virtual place because the one included alredy in the memory is +%not updated with the last signature +[memory(1,:) dictionary] = updateCommonSignature(memory, dictionary); + +sign = memory(end,:); +r = sum(sum((sign - [82,9,24,27,37,39,40,45,46,64,64,67,67,68,68,69,69,79,79,81,85,87,95,109,113,115,117,122,124,127,135,135,139,142,143,145,157,159,161,168,181,188,188,191,192,196,197,208,208,211,213,219,239,241,247,256,257,258,307,310,343,558,587,643,760,896,960,1019,1023,1074,1197,1207,1292,1401,1403,1587,2151,2419,2571,2623,2654,2664,2674,2674,2674,2777,2780,2796,2808,2835,2844,2844,2851,2855,2862,2900,3036,3143,3238,3238,3315,3531,3531,3766,3803,4296,4405,4462,4502,4506,4509,4509,4516,4523,4533,4533,4534,4535,4536,4537,4538,4539,4540,4541,4542,4543,4544,4545,4546,4547,4548,4549,4550,4551,4552,4553,4554,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0;]) ~= 0)); +if r ~= 0 + error('sign is not valid!') +end + +likelihood = computeLikelihood(sign, memory, dictionary); +plot(likelihood) +likelihood = floor(likelihood*1000) +likelihood = likelihood'; + +r = sum(sum((likelihood - [109,157,263,203,87,66,78,49,60,40,47,43,43,55,102,102,147,0,38,61,64,74,69,103,39,20,44,33,14,14,20,12,18,8,59,19,41,26,45,117,124,173,223,74,0,10,17,53,33,24,33,43,52,68,119,124,146,159,28,68,59,115,71,95,37,18,16,49,9,28,20,9,15,11,10,35,45,73,18,92,167,219,1222;]) ~= 0)); +if r ~= 0 + error('likelihood is not valid!') +end diff --git a/Matlab/Tests/TestUpdateCommonSignature.m b/Matlab/Tests/TestUpdateCommonSignature.m new file mode 100644 index 0000000000..8fb9d2f2e6 --- /dev/null +++ b/Matlab/Tests/TestUpdateCommonSignature.m @@ -0,0 +1,27 @@ +close all +clear all + +% Test AvpdCore::KeypointMemory::updateCommonSignature() + +% load data +memory = dlmread('090306-3_db-Signatures.txt', ' ', 1, 0); +dictionary = dlmread('090306-3_db-Dictionary.txt', ' ', 1, 0); + +[virtualPlace dictionary] = updateCommonSignature(memory, dictionary); + +%just sorting words, putting the zeros at the end +indexZero = find(virtualPlace == 0,1); +if isempty(indexZero) + indexZero = length(virtualPlace); +elseif indexZero>1 + indexZero = indexZero-1; +end +virtualPlace(2:indexZero) = sort(virtualPlace(2:indexZero)); +commonWords = virtualPlace(2:indexZero) +disp('Total common words:') +disp(length(commonWords)) + +r = sum(sum((commonWords - [5,8,9,21,22,23,27,30,32,34,36,40,45,53,61,62,64,67,68,77,85,86,97,98,99,100,103,106,122,124,125,127,129,131,143,157,161,164,168,169,172,175,181,182,187,196,197,208,210,217,218,219,220,234,235,237,244,252,286,307,308,310,315,327,328,346,347,350,355,362,372,373,387,393,394,398,404,412,423,428,429,441,442,456,465,470,476,495,496,498,506,520,558,572,584,585,587,596,620,634,637,643,646,647,658,667,668,681,709,721,726,741,766,777,785,790,791,793,808,809,880,896,906,908,919,933,940,954,958,974,981,1002,1023,1026,1058,1060,1071,1074,1097,1146,1152,1166,1215,1217,1229,1334,1341,1387,1478,1510,1539,1549,1566,1601,1624,1649,1693,1814,2046,2141,2424,2442,2674;]) ~= 0)); +if r ~= 0 + error('commonWords is not valid!') +end diff --git a/Matlab/adjustLikelihood.m b/Matlab/adjustLikelihood.m new file mode 100644 index 0000000000..6755054b45 --- /dev/null +++ b/Matlab/adjustLikelihood.m @@ -0,0 +1,17 @@ +function LN = adjustLikelihood(L) +%ADJUSTLIKELIHOOD Adjust the likelihood with std dev and mean. +% LN = adjustLikelihood(L) +% +% L the likelihood (m,1) +% LN the likelihood normalized (m,1) + +m = mean(L); % Calcul mean +s = std(L); % Calcul std dev +LN = zeros(size(L)); +for i=1:length(L) + if L(i) > m + s + LN(i) = (L(i)-s)/m; + else + LN(i) = 1; + end +end \ No newline at end of file diff --git a/Matlab/computeLikelihood.m b/Matlab/computeLikelihood.m new file mode 100644 index 0000000000..5a98a38d3f --- /dev/null +++ b/Matlab/computeLikelihood.m @@ -0,0 +1,46 @@ +function likelihood = computeLikelihood(sign, memory, dictionary) +%GETLIKELIHOOD Compute the likelihood of the signature sign. +% likelihood = getLikelihood(sign, memory, dictionary) +% +% Mem : the memory (Mem = [signId1 WordRefIds...; signId2 WordRefIds...; ...]) +% Dict : the visual words dictionary (Dict = [wordId1 SignRefIds...; +% wordId2 SignRefIds...; ...]) +% sign : The signature reference which we want the likelihood with all +% others in the memory (sign = [id wordsRef...]) +likelihood = zeros(size(memory,1),1); + +nwi = 0; % nwi is the number of a specific word referenced by a place +ni = 0; % ni is the total of words referenced by a place +nw = 0; % nw is the number of places referenced by a specific word +N = 0; % N is the total number of places + +N = size(memory,1); + +words = unique(sign(2:end)); + +for i=1:length(words) + if words(i) ~= 0 + % "Inverted index" - For each places referenced by the word + refs = unique(dictionary(find(dictionary(:,1) == words(i),1), 2:end)); + refs = refs(refs~=0); + nw = 0; + for j=1:length(refs) + nw = nw + 1; + end + logNnw = log10(N/nw); % TODO : division by 0 (not supposed to occur) + + if logNnw ~= 0 + for j=1:length(refs) + pos = find(memory(:,1) == refs(j),1); + sign = memory(pos, :); + + nwi = sum(sign(2:end) == words(i)); + ni = sum(sign(2:end)>0); + + if ni ~= 0 + likelihood(pos) = likelihood(pos) + ( nwi * logNnw ) / ni; + end + end + end + end +end \ No newline at end of file diff --git a/Matlab/generatePrediction.m b/Matlab/generatePrediction.m new file mode 100644 index 0000000000..4663b79fd8 --- /dev/null +++ b/Matlab/generatePrediction.m @@ -0,0 +1,85 @@ +function P = generatePrediction(NewPlacePrediction, LoopClosurePrediction, m) +% GENERATEPREDICTION Generate a prediction matrix P(m+1,m+1) for the Bayes +% filter +% P = generatePrediction(NewPlacePrediction, LoopClosurePrediction, m) +% +% P(1,:) is the prediction for the "no loop closure event" using the +% NoLoopClosurePrediction. NewPlacePrediction is a number between 0 +% and 1. +% +% P(2:end, :) is the predictions for each m "loop closure event" using the +% LoopClosurePrediction pattern. Format LoopClosurePrediction: +% [virtualPlace LoopCLosure neighbor-1 neighbor+1 neighbor-2 +% neighbor+2...] +% +% Example: +% predictionLC = [0.1 0.19 0.24 0.24 0.1 0.1 0.01 0.01]; +% virtualPlacePrior = 0.8; +% m = 10; %We have 10 places +% P = generatePrediction(virtualPlacePrior, predictionLC, m); +P = zeros(m+1,m+1); +if NewPlacePrediction<0 || NewPlacePrediction>1 + error(['NoLoopClosurePrediction=' num2str(NewPlacePrediction) ' > 1 or < 0!']); +end +if sum(LoopClosurePrediction) > 1 + error(['sum(LoopClosurePrediction)=' num2str(sum(LoopClosurePrediction)) ' > 1 or < 0!']); +elseif(sum(LoopClosurePrediction) == 1) + warning(['sum(LoopClosurePrediction)=' num2str(sum(LoopClosurePrediction)) ' == 1, all probabilities will be zero for non-neighbors']); +end +P(1,:) = [NewPlacePrediction ones(1,m)*(1-NewPlacePrediction)/(m)]; +predictionLC = LoopClosurePrediction; +for i=2:m+1 + y = zeros(1,m+1); + + loopClosureId = i; + + % The first must be the virtual place + y(1) = predictionLC(1); + + % Set all others to a small value + y(2:length(y)) = (1-sum(predictionLC))/m; + + % Set high values (gaussians curves) to loop closure neighbors + probAdded = 0; + % LoopID + y(i) = y(i) + predictionLC(2); %0.175 + probAdded = probAdded + predictionLC(2); + + % look up backward for each neighbors + n = loopClosureId; + for k=3:2:length(predictionLC) + n = n-1; + if n > 1 + y(n) = y(n) + predictionLC(k); + probAdded = probAdded + predictionLC(k); + else + break; + end + end + + % look up forward for each neighbors + n = loopClosureId; + for k=4:2:length(predictionLC) + n = n+1; + if n <= length(y) + y(n) = y(n) + predictionLC(k); + probAdded = probAdded + predictionLC(k); + else + break; + end + end + + % add values not set (they are forgotten) to the loop id + totalLCProb = sum(predictionLC(2:length(predictionLC))); + if(probAdded >= 0 && probAdded < totalLCProb) + y(i) = y(i) + totalLCProb - probAdded; + elseif(probAdded > totalLCProb+0.001) + error(['probAdded=' num2str(probAdded) ' > ' num2str(totalLCProb) ' ?']); + end + + P(i,:) = y; + sum(P(i,:)); + if sum(P(i,:)) < 1-0.001 || sum(P(i,:)) > 1+0.001 + error(['sum of the resulting pdf is not one (' num2str(sum(P(i,:))) ')!']); + end +end \ No newline at end of file diff --git a/Matlab/updateCommonSignature.m b/Matlab/updateCommonSignature.m new file mode 100644 index 0000000000..3068c9dc15 --- /dev/null +++ b/Matlab/updateCommonSignature.m @@ -0,0 +1,69 @@ +function [CS DU] = updateCommonSignature(Mem, Dict) +%UPDATECOMMONSIGNATURE Update the common signature (virtual place) +% [CS DU] = updateCommonSignature(Mem, Dict) +% +% CS = common signature +% DU dictionary updated +% Mem : the memory (Mem = [signId1 WordRefIds...; signId2 WordRefIds...; ...]) +% Dict : the visual words dictionary (Dict = [wordId1 SignRefIds...; wordId2 SignRefIds...; ...]) + +CS = Mem(1,:); +CSid = Mem(1,1); + +% Clear references to the virtual place +for i=2:length(CS) + index = find(Dict(:,1) == CS(i)); + if isempty(index) ~= 1 + indexes = find(Dict(index, :) == CSid); + for j=1:length(indexes) + Dict(index,indexes(j)) = 0; + end + end +end +%clear words in the common signature +CS = CS(1); + +%How many words we want... take the average of words by signature +nbCommonWords = 0; +memSize = size(Mem,1)-1; %Don't count the virtual place +if(memSize > 0) + totalActiveRef = sum(sum(Dict(:,2:end) ~= 0)); + nbCommonWords = floor(totalActiveRef / memSize); +end +if nbCommonWords>0 + %get common words + list = [(sum(Dict(:,2:end)' ~= 0)') Dict(:,1)]; + list = sortrows(list) + wordsAdded=0; + for i=size(list,1):-1:1 + if i ~= size(list,1) && length(CS)>1 + ratio = floor(list(i+1,1)/list(i,1)); + len = length(CS); + for j=2:ratio + for k=2:len + CS = [CS CS(k)]; + wordsAdded = wordsAdded + 1; + if wordsAdded >= nbCommonWords + break; + end + end + if wordsAdded >= nbCommonWords + break; + end + end + end + + if wordsAdded < nbCommonWords + CS = [CS list(i,2)]; + wordsAdded = wordsAdded + 1; + end + if wordsAdded >= nbCommonWords + break; + end + end + CS = [CS zeros(1,size(Mem,2) - length(CS))]; + % add references + DU = updateDictionary(Dict, CS); +else + DU = Dict; +end \ No newline at end of file diff --git a/Matlab/updateDictionary.m b/Matlab/updateDictionary.m new file mode 100644 index 0000000000..c64459a5b7 --- /dev/null +++ b/Matlab/updateDictionary.m @@ -0,0 +1,29 @@ +function DU = updateDictionary(Dict, Sign) +%UPDATEDICTIONARY Update the dictionary with the new signature (place) +% DU = updateDictionary(Dict, Sign) +% +% DU = dictionary updated +% dictionary = [wordId1 SignRefIds...; wordId2 SignRefIds...; ...] +% sign = [signId WordRefIds...] + +signId = Sign(1); +Sign = Sign(Sign~=0); +for i=2:length(Sign) + indexWord = []; + if isempty(Dict) ~= 1 + indexWord = find(Dict(:,1) == Sign(i),1); + end + if isempty(indexWord) == 1 + Dict(size(Dict,1)+1,1) = Sign(i); %Word id + Dict(size(Dict,1),2) = signId; %Signature id ref + else + ii = 1:length(Dict(indexWord,1:end)); + indexesZero = ii(Dict(indexWord,1:end) == 0); + if isempty(indexesZero) + Dict(indexWord, size(Dict,2)+1) = signId; + else + Dict(indexWord, indexesZero(1)) = signId; + end + end +end +DU = Dict; \ No newline at end of file diff --git a/README b/README new file mode 100644 index 0000000000..630668a91a --- /dev/null +++ b/README @@ -0,0 +1,116 @@ ++--------------------------------------------------- ++ Contents ++--------------------------------------------------- + - Requirements + - Building from source + - Building with Eclipse + - Issues + - Notes + - Packaging + - Source packaging + - Uninstall + ++--------------------------------------------------- ++ Requirements ++--------------------------------------------------- + - CMake + - OpenCV + - UtiLite library (http://code.google.com/p/utilite/) + - sqlite3 (only for Unix, the win32 binaries are already added to this project) + - MinGW (only for Windows) + + Optionals : + - [Highly recommended!] Qt4 open source Unix or MinGW (for Windows) to build the GUI + - cppunit (only for Unix, the win32 binaries are already added to this project) + +Note : For Windows users, don't forget to modify the PATH environnement variable. + ++--------------------------------------------------- ++ Building from source ++--------------------------------------------------- + [Unix] + > cd build + > cmake .. + > make + > sudo make install + + [Win32] + > cd build + > cmake -G "MinGW Makefiles" .. + > mingw32-make + > mingw32-make install + +Note : Output destination is in {project_root}/bin and {project_root}/lib of this project. + ++--------------------------------------------------- ++ Building with Eclipse with CDT (c/c++ plugin) ++--------------------------------------------------- + 1- Eclipse -> Import existing project -> select this directory + 2- Eclipse -> Make targets -> Avpd -> execute CMake-Unix-Release (CMake-MinGW-Release on Windows) + 3- Eclipse -> Project -> Build All + + - The AutoCompletion should already works... (Project->Properties->C/C++Build->Discovery options) + ++--------------------------------------------------- ++ Issues ++--------------------------------------------------- + - Webcam issue (Linux): + > sudo addgroup UserName video + reboot! + or + > sudo chmod 666 /dev/video0 + + - Video can't be initiated (*.avi) by OpenCV (Linux) : + Make sure that OpenCV is builded with ffmpeg (ROS binary "turtleBox" openCV package doesn't build with it?!) + ++--------------------------------------------------- ++ Notes ++--------------------------------------------------- +Memory leaks check (Linux): + >valgrind --tool=memcheck --leak-check=yes ./testCoreLib + There are some memory errors with vfprintf() or write(), but the more important is at the end of the report in the summary "LEAK SUMMARY". + ++--------------------------------------------------- ++ Packaging ++--------------------------------------------------- +MacOSX (DragNDrop) : + > cd build + > cmake -DCMAKE_INSTALL_PREFIX="/" -DBUILD_AS_BUNDLE=ON .. + > make -j4 + > make package + +MacOSX (.tar.bz2) : + > cd build + > cmake -DBUILD_AS_BUNDLE=OFF .. + > make -j4 + > make package + +Ubuntu (.deb, .tar.bz2) : + > cd build + > cmake .. + > make -j4 + > make package + +Windows (nsis-exe, zip) : + (Using Visual Studio Command Prompt to be able to use 'dumpbin') + > cd build + > cmake -G"MinGW Makefiles" .. + > mingw32-make -j4 + > mingw32-make package + ++--------------------------------------------------- ++ Source packaging ++--------------------------------------------------- +UNIX > make package_source +WIN32 > mingw32-make package_source + ++--------------------------------------------------- ++ Uninstall ++--------------------------------------------------- +A target is provided when building from source : + UNIX > make uninstall + WIN32 > mingw32-make uninstall + +Other files : + Configuration file : {Home_folder}/.rtabmap + Working directory : {Home_folder}/Documents/RTAB-Map \ No newline at end of file diff --git a/Version.h.in b/Version.h.in new file mode 100644 index 0000000000..a3a624742b --- /dev/null +++ b/Version.h.in @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef VERSION_H_ +#define VERSION_H_ + +// This is auto-generated! +#define RTABMAP_VERSION "@PROJECT_VERSION@"; + +#define RTABMAP_VERSION_MAJOR @PROJECT_VERSION_MAJOR@; +#define RTABMAP_VERSION_MINOR @PROJECT_VERSION_MINOR@; +#define RTABMAP_VERSION_PATCH @PROJECT_VERSION_PATCH@; + +#define RTABMAP_VERSION_COMPARE(major, minor, patch) major>=@PROJECT_VERSION_MAJOR@ && minor>=@PROJECT_VERSION_MINOR@ && patch >=@PROJECT_VERSION_PATCH@ + +#endif /* VERSION_H_ */ + diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt new file mode 100644 index 0000000000..1d6f03b459 --- /dev/null +++ b/app/CMakeLists.txt @@ -0,0 +1 @@ +ADD_SUBDIRECTORY( src ) \ No newline at end of file diff --git a/app/src/CMakeLists.txt b/app/src/CMakeLists.txt new file mode 100644 index 0000000000..9613713222 --- /dev/null +++ b/app/src/CMakeLists.txt @@ -0,0 +1,130 @@ + +### Qt Gui stuff ### +SET(headers_ui + ./ObjDeletionHandler.h +) + +#This will generate moc_* for Qt +QT4_WRAP_CPP(moc_srcs ${headers_ui}) + +SET(SRC_FILES + main.cpp + ${moc_srcs} +) + +SET(INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR} + ${UTILITE_INCLUDE_DIR} + ${OpenCV_INCLUDE_DIRS} +) + +INCLUDE(${QT_USE_FILE}) + +SET(LIBRARIES + ${UTILITE_LIBRARY} + ${QT_LIBRARIES} + ${OpenCV_LIBS} +) + +# Make sure the compiler can find include files from our library. +INCLUDE_DIRECTORIES(${INCLUDE_DIRS}) + +# For Apple set the icns file containing icons +IF(APPLE AND BUILD_AS_BUNDLE) + # set how it shows up in the Info.plist file + SET(MACOSX_BUNDLE_ICON_FILE ${PROJECT_NAME}.icns) + # set where in the bundle to put the icns file + SET_SOURCE_FILES_PROPERTIES(${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + # include the icns file in the target + SET(SRC_FILES ${SRC_FILES} ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.icns) +ENDIF(APPLE AND BUILD_AS_BUNDLE) + +# Add exe icon resource +IF(WIN32) + IF( MINGW ) + # resource compilation for MinGW + ADD_CUSTOM_COMMAND( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/AppIco.o + COMMAND windres.exe -I${CMAKE_CURRENT_SOURCE_DIR} -i${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.rc + -o ${CMAKE_CURRENT_BINARY_DIR}/AppIco.o ) + SET(SRC_FILES ${SRC_FILES} ${CMAKE_CURRENT_BINARY_DIR}/AppIco.o) + ELSE( MINGW ) + SET(SRC_FILES ${SRC_FILES} ${PROJECT_NAME}.rc) + ENDIF( MINGW ) +ENDIF(WIN32) + +# Add binary +IF(APPLE AND BUILD_AS_BUNDLE) + ADD_EXECUTABLE(main_app MACOSX_BUNDLE ${SRC_FILES}) +ELSE() + ADD_EXECUTABLE(main_app WIN32 ${SRC_FILES}) +ENDIF() +TARGET_LINK_LIBRARIES(main_app corelib guilib ${LIBRARIES}) + +IF(APPLE AND BUILD_AS_BUNDLE) + SET_TARGET_PROPERTIES(main_app PROPERTIES + OUTPUT_NAME ${CMAKE_BUNDLE_NAME}) +ELSEIF(WIN32) + SET_TARGET_PROPERTIES(main_app PROPERTIES + OUTPUT_NAME ${PROJECT_NAME}) +ELSE() + SET_TARGET_PROPERTIES(main_app PROPERTIES + OUTPUT_NAME ${PROJECT_PREFIX}) +ENDIF() + +#--------------------------- +# Installation stuff +#--------------------------- +INSTALL(TARGETS main_app + RUNTIME DESTINATION bin COMPONENT runtime + BUNDLE DESTINATION "${CMAKE_BUNDLE_LOCATION}" COMPONENT runtime) + +IF(APPLE AND BUILD_AS_BUNDLE) +INSTALL(CODE "execute_process(COMMAND ln -s \"../MacOS/${CMAKE_BUNDLE_NAME}\" ${PROJECT_NAME} + WORKING_DIRECTORY \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin)") +ENDIF(APPLE AND BUILD_AS_BUNDLE) + +SET(APPS "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}") +IF((APPLE AND BUILD_AS_BUNDLE) OR WIN32) + SET(plugin_dest_dir bin) + SET(qtconf_dest_dir bin) + IF(APPLE) + SET(plugin_dest_dir MacOS) + SET(qtconf_dest_dir Resources) + SET(APPS "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/MacOS/${CMAKE_BUNDLE_NAME}") + ENDIF(APPLE) + + # Install needed Qt plugins by copying directories from the qt installation + # One can cull what gets copied by using 'REGEX "..." EXCLUDE' + # Exclude debug libraries + INSTALL(DIRECTORY "${QT_PLUGINS_DIR}/imageformats" + DESTINATION ${plugin_dest_dir}/plugins + COMPONENT runtime + REGEX ".*d4.dll" EXCLUDE + REGEX ".*d4.a" EXCLUDE) + + # install a qt.conf file + # this inserts some cmake code into the install script to write the file + SET(QT_CONF_FILE [Paths]\nPlugins=plugins) + IF(APPLE) + SET(QT_CONF_FILE [Paths]\nPlugins=MacOS/plugins) + ENDIF(APPLE) + INSTALL(CODE " + file(WRITE \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${qtconf_dest_dir}/qt.conf\" \"${QT_CONF_FILE}\") + " COMPONENT runtime) + + # directories to look for dependencies + SET(DIRS ${QT_LIBRARY_DIRS} ${PROJECT_SOURCE_DIR}/bin) + + # Now the work of copying dependencies into the bundle/package + # The quotes are escaped and variables to use at install time have their $ escaped + # An alternative is the do a configure_file() on a script and use install(SCRIPT ...). + # Note that the image plugins depend on QtSvg and QtXml, and it got those copied + # over. + # To find dependencies, cmake use "otool" on Apple and "dumpbin" on Windows (make sure you have one of them). + install(CODE " + file(GLOB_RECURSE QTPLUGINS \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/plugins/*${CMAKE_SHARED_LIBRARY_SUFFIX}\") + include(\"BundleUtilities\") + fixup_bundle(\"${APPS}\" \"\${QTPLUGINS}\" \"${DIRS}\") + " COMPONENT runtime) +ENDIF((APPLE AND BUILD_AS_BUNDLE) OR WIN32) + diff --git a/app/src/ObjDeletionHandler.h b/app/src/ObjDeletionHandler.h new file mode 100644 index 0000000000..c49b8b8f05 --- /dev/null +++ b/app/src/ObjDeletionHandler.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef OBJDELETIONHANDLER_H_ +#define OBJDELETIONHANDLER_H_ + +#include "utilite/UEventsHandler.h" +#include "utilite/UEvent.h" +#include + +class ObjDeletionHandler : public QObject, public UEventsHandler +{ + Q_OBJECT + +public: + ObjDeletionHandler(int watchedId, QObject * receiver = 0, const char * member = 0) : _watchedId(watchedId) + { + if(receiver && member) + { + connect(this, SIGNAL(objDeletionEventReceived(int)), receiver, member); + } + } + virtual ~ObjDeletionHandler() {} + +signals: + void objDeletionEventReceived(int); + +protected: + virtual void handleEvent(UEvent * event) + { + if(event->getClassName().compare("ObjDeletedEvent") == 0 && + event->getCode() == _watchedId) + { + emit objDeletionEventReceived(_watchedId); + } + } +private: + int _watchedId; +}; + +#endif /* OBJDELETIONHANDLER_H_ */ diff --git a/app/src/RTAB-Map.icns b/app/src/RTAB-Map.icns new file mode 100644 index 0000000000..8747cc5ebe Binary files /dev/null and b/app/src/RTAB-Map.icns differ diff --git a/app/src/RTAB-Map.ico b/app/src/RTAB-Map.ico new file mode 100644 index 0000000000..eb277e91a4 Binary files /dev/null and b/app/src/RTAB-Map.ico differ diff --git a/app/src/RTAB-Map.rc b/app/src/RTAB-Map.rc new file mode 100644 index 0000000000..f120312068 --- /dev/null +++ b/app/src/RTAB-Map.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "RTAB-Map.ico" diff --git a/app/src/main.cpp b/app/src/main.cpp new file mode 100644 index 0000000000..a92c008a25 --- /dev/null +++ b/app/src/main.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include +#include +#include "utilite/UEventsManager.h" +#include "rtabmap/core/Rtabmap.h" +#include "rtabmap/gui/MainWindow.h" +#include +#include "utilite/UObjDeletionThread.h" +#include "ObjDeletionHandler.h" + +using namespace rtabmap; + +int main(int argc, char* argv[]) +{ + + /* Set logger type */ + ULogger::setType(ULogger::kTypeConsole); + ULogger::setLevel(ULogger::kWarning); // Disable log with level + + ULOGGER_INFO("Program started..."); + + /* Create tasks */ + Rtabmap * rtabmap = new Rtabmap(); + QApplication * app = new QApplication(argc, argv); + MainWindow * mainWindow = new MainWindow(); + + /* Add handlers to the EventsManager */ + UEventsManager::addHandler(mainWindow); + UEventsManager::addHandler(rtabmap); //thread + + /* Start thread's task */ + mainWindow->showNormal(); + + // Now wait for application to finish + app->connect( app, SIGNAL( lastWindowClosed() ), + app, SLOT( quit() ) ); + app->exec();// MUST be called by the Main Thread + + /* Remove handlers */ + UEventsManager::removeHandler(mainWindow); + UEventsManager::removeHandler(rtabmap); + + ULOGGER_INFO("Killing threads..."); + rtabmap->kill(); + + ULOGGER_INFO("Free memory..."); + + delete mainWindow; + //Since we can't put the Rtabmap object in the MainWindow class, + //we pop up a message box indicating that the rtabmap object + // is being deleted (saving data to the database) + QMessageBox * msg = new QMessageBox(QMessageBox::Information, QObject::tr("RTAB-Map is closing..."), QObject::tr("The detector is saving the working memory to database (located in RTAB-Map's working directory)..."), QMessageBox::NoButton); + msg->setEnabled(false); + msg->setIconPixmap(QPixmap(":/images/RTAB-Map.ico")); + msg->setWindowIcon(QIcon(":/images/RTAB-Map.ico")); + msg->show(); + ObjDeletionThread delThread(rtabmap); + ObjDeletionHandler handler(delThread.id(), app, SLOT(quit())); + UEventsManager::addHandler(&handler); + delThread.startDeletion(1); // make sure that app-exec() is called before the deletion of the object + app->exec(); + delete msg; + delete app; + + ULOGGER_INFO("All done! Closing..."); + + return 0; +} diff --git a/bin/data/090206-3.db b/bin/data/090206-3.db new file mode 100644 index 0000000000..4badeac492 Binary files /dev/null and b/bin/data/090206-3.db differ diff --git a/bin/data/090206-3/1.jpg b/bin/data/090206-3/1.jpg new file mode 100644 index 0000000000..dbf9e86c0f Binary files /dev/null and b/bin/data/090206-3/1.jpg differ diff --git a/bin/data/090206-3/10.jpg b/bin/data/090206-3/10.jpg new file mode 100644 index 0000000000..b0e3e9554a Binary files /dev/null and b/bin/data/090206-3/10.jpg differ diff --git a/bin/data/090206-3/11.jpg b/bin/data/090206-3/11.jpg new file mode 100644 index 0000000000..c0c0de78ce Binary files /dev/null and b/bin/data/090206-3/11.jpg differ diff --git a/bin/data/090206-3/12.jpg b/bin/data/090206-3/12.jpg new file mode 100644 index 0000000000..64a004fa5c Binary files /dev/null and b/bin/data/090206-3/12.jpg differ diff --git a/bin/data/090206-3/13.jpg b/bin/data/090206-3/13.jpg new file mode 100644 index 0000000000..9388f62a53 Binary files /dev/null and b/bin/data/090206-3/13.jpg differ diff --git a/bin/data/090206-3/14.jpg b/bin/data/090206-3/14.jpg new file mode 100644 index 0000000000..49e98626e9 Binary files /dev/null and b/bin/data/090206-3/14.jpg differ diff --git a/bin/data/090206-3/15.jpg b/bin/data/090206-3/15.jpg new file mode 100644 index 0000000000..6b529382e6 Binary files /dev/null and b/bin/data/090206-3/15.jpg differ diff --git a/bin/data/090206-3/16.jpg b/bin/data/090206-3/16.jpg new file mode 100644 index 0000000000..f5e9a75298 Binary files /dev/null and b/bin/data/090206-3/16.jpg differ diff --git a/bin/data/090206-3/17.jpg b/bin/data/090206-3/17.jpg new file mode 100644 index 0000000000..1b22421620 Binary files /dev/null and b/bin/data/090206-3/17.jpg differ diff --git a/bin/data/090206-3/18.jpg b/bin/data/090206-3/18.jpg new file mode 100644 index 0000000000..9b57152541 Binary files /dev/null and b/bin/data/090206-3/18.jpg differ diff --git a/bin/data/090206-3/19.jpg b/bin/data/090206-3/19.jpg new file mode 100644 index 0000000000..185d1f981f Binary files /dev/null and b/bin/data/090206-3/19.jpg differ diff --git a/bin/data/090206-3/2.jpg b/bin/data/090206-3/2.jpg new file mode 100644 index 0000000000..aea1afa87c Binary files /dev/null and b/bin/data/090206-3/2.jpg differ diff --git a/bin/data/090206-3/20.jpg b/bin/data/090206-3/20.jpg new file mode 100644 index 0000000000..bce1fb7e5e Binary files /dev/null and b/bin/data/090206-3/20.jpg differ diff --git a/bin/data/090206-3/21.jpg b/bin/data/090206-3/21.jpg new file mode 100644 index 0000000000..ae77be2a2d Binary files /dev/null and b/bin/data/090206-3/21.jpg differ diff --git a/bin/data/090206-3/22.jpg b/bin/data/090206-3/22.jpg new file mode 100644 index 0000000000..7e6458d228 Binary files /dev/null and b/bin/data/090206-3/22.jpg differ diff --git a/bin/data/090206-3/23.jpg b/bin/data/090206-3/23.jpg new file mode 100644 index 0000000000..35fffb7571 Binary files /dev/null and b/bin/data/090206-3/23.jpg differ diff --git a/bin/data/090206-3/24.jpg b/bin/data/090206-3/24.jpg new file mode 100644 index 0000000000..d042595dc1 Binary files /dev/null and b/bin/data/090206-3/24.jpg differ diff --git a/bin/data/090206-3/25.jpg b/bin/data/090206-3/25.jpg new file mode 100644 index 0000000000..5a6572f448 Binary files /dev/null and b/bin/data/090206-3/25.jpg differ diff --git a/bin/data/090206-3/26.jpg b/bin/data/090206-3/26.jpg new file mode 100644 index 0000000000..038602ee58 Binary files /dev/null and b/bin/data/090206-3/26.jpg differ diff --git a/bin/data/090206-3/27.jpg b/bin/data/090206-3/27.jpg new file mode 100644 index 0000000000..d3b5477957 Binary files /dev/null and b/bin/data/090206-3/27.jpg differ diff --git a/bin/data/090206-3/28.jpg b/bin/data/090206-3/28.jpg new file mode 100644 index 0000000000..3cad95259a Binary files /dev/null and b/bin/data/090206-3/28.jpg differ diff --git a/bin/data/090206-3/29.jpg b/bin/data/090206-3/29.jpg new file mode 100644 index 0000000000..39fe9f9a7d Binary files /dev/null and b/bin/data/090206-3/29.jpg differ diff --git a/bin/data/090206-3/3.jpg b/bin/data/090206-3/3.jpg new file mode 100644 index 0000000000..c2abc45bd4 Binary files /dev/null and b/bin/data/090206-3/3.jpg differ diff --git a/bin/data/090206-3/30.jpg b/bin/data/090206-3/30.jpg new file mode 100644 index 0000000000..2a99878460 Binary files /dev/null and b/bin/data/090206-3/30.jpg differ diff --git a/bin/data/090206-3/31.jpg b/bin/data/090206-3/31.jpg new file mode 100644 index 0000000000..fe676fe314 Binary files /dev/null and b/bin/data/090206-3/31.jpg differ diff --git a/bin/data/090206-3/32.jpg b/bin/data/090206-3/32.jpg new file mode 100644 index 0000000000..a494986571 Binary files /dev/null and b/bin/data/090206-3/32.jpg differ diff --git a/bin/data/090206-3/33.jpg b/bin/data/090206-3/33.jpg new file mode 100644 index 0000000000..6e49144370 Binary files /dev/null and b/bin/data/090206-3/33.jpg differ diff --git a/bin/data/090206-3/34.jpg b/bin/data/090206-3/34.jpg new file mode 100644 index 0000000000..5616722a98 Binary files /dev/null and b/bin/data/090206-3/34.jpg differ diff --git a/bin/data/090206-3/35.jpg b/bin/data/090206-3/35.jpg new file mode 100644 index 0000000000..3fb950cad9 Binary files /dev/null and b/bin/data/090206-3/35.jpg differ diff --git a/bin/data/090206-3/36.jpg b/bin/data/090206-3/36.jpg new file mode 100644 index 0000000000..fd06d2cb85 Binary files /dev/null and b/bin/data/090206-3/36.jpg differ diff --git a/bin/data/090206-3/37.jpg b/bin/data/090206-3/37.jpg new file mode 100644 index 0000000000..5f3831b896 Binary files /dev/null and b/bin/data/090206-3/37.jpg differ diff --git a/bin/data/090206-3/38.jpg b/bin/data/090206-3/38.jpg new file mode 100644 index 0000000000..2d8ad0d316 Binary files /dev/null and b/bin/data/090206-3/38.jpg differ diff --git a/bin/data/090206-3/39.jpg b/bin/data/090206-3/39.jpg new file mode 100644 index 0000000000..ca708bed85 Binary files /dev/null and b/bin/data/090206-3/39.jpg differ diff --git a/bin/data/090206-3/4.jpg b/bin/data/090206-3/4.jpg new file mode 100644 index 0000000000..5d8d5a864c Binary files /dev/null and b/bin/data/090206-3/4.jpg differ diff --git a/bin/data/090206-3/40.jpg b/bin/data/090206-3/40.jpg new file mode 100644 index 0000000000..6ac100af6b Binary files /dev/null and b/bin/data/090206-3/40.jpg differ diff --git a/bin/data/090206-3/41.jpg b/bin/data/090206-3/41.jpg new file mode 100644 index 0000000000..03bdfbb73b Binary files /dev/null and b/bin/data/090206-3/41.jpg differ diff --git a/bin/data/090206-3/42.jpg b/bin/data/090206-3/42.jpg new file mode 100644 index 0000000000..bb195bdf92 Binary files /dev/null and b/bin/data/090206-3/42.jpg differ diff --git a/bin/data/090206-3/43.jpg b/bin/data/090206-3/43.jpg new file mode 100644 index 0000000000..5b7ae15f46 Binary files /dev/null and b/bin/data/090206-3/43.jpg differ diff --git a/bin/data/090206-3/44.jpg b/bin/data/090206-3/44.jpg new file mode 100644 index 0000000000..3ec009600e Binary files /dev/null and b/bin/data/090206-3/44.jpg differ diff --git a/bin/data/090206-3/45.jpg b/bin/data/090206-3/45.jpg new file mode 100644 index 0000000000..13e89f9e0e Binary files /dev/null and b/bin/data/090206-3/45.jpg differ diff --git a/bin/data/090206-3/46.jpg b/bin/data/090206-3/46.jpg new file mode 100644 index 0000000000..9b42463d8a Binary files /dev/null and b/bin/data/090206-3/46.jpg differ diff --git a/bin/data/090206-3/47.jpg b/bin/data/090206-3/47.jpg new file mode 100644 index 0000000000..3b5af49ddb Binary files /dev/null and b/bin/data/090206-3/47.jpg differ diff --git a/bin/data/090206-3/48.jpg b/bin/data/090206-3/48.jpg new file mode 100644 index 0000000000..f07fd4dae1 Binary files /dev/null and b/bin/data/090206-3/48.jpg differ diff --git a/bin/data/090206-3/49.jpg b/bin/data/090206-3/49.jpg new file mode 100644 index 0000000000..ffaaa669d3 Binary files /dev/null and b/bin/data/090206-3/49.jpg differ diff --git a/bin/data/090206-3/5.jpg b/bin/data/090206-3/5.jpg new file mode 100644 index 0000000000..1ae7bca64d Binary files /dev/null and b/bin/data/090206-3/5.jpg differ diff --git a/bin/data/090206-3/50.jpg b/bin/data/090206-3/50.jpg new file mode 100644 index 0000000000..2948aa38e3 Binary files /dev/null and b/bin/data/090206-3/50.jpg differ diff --git a/bin/data/090206-3/51.jpg b/bin/data/090206-3/51.jpg new file mode 100644 index 0000000000..cac13644b1 Binary files /dev/null and b/bin/data/090206-3/51.jpg differ diff --git a/bin/data/090206-3/52.jpg b/bin/data/090206-3/52.jpg new file mode 100644 index 0000000000..8d56734817 Binary files /dev/null and b/bin/data/090206-3/52.jpg differ diff --git a/bin/data/090206-3/53.jpg b/bin/data/090206-3/53.jpg new file mode 100644 index 0000000000..93b0de3585 Binary files /dev/null and b/bin/data/090206-3/53.jpg differ diff --git a/bin/data/090206-3/54.jpg b/bin/data/090206-3/54.jpg new file mode 100644 index 0000000000..16b013e1f4 Binary files /dev/null and b/bin/data/090206-3/54.jpg differ diff --git a/bin/data/090206-3/55.jpg b/bin/data/090206-3/55.jpg new file mode 100644 index 0000000000..44e1ac1315 Binary files /dev/null and b/bin/data/090206-3/55.jpg differ diff --git a/bin/data/090206-3/56.jpg b/bin/data/090206-3/56.jpg new file mode 100644 index 0000000000..f4cb1211ce Binary files /dev/null and b/bin/data/090206-3/56.jpg differ diff --git a/bin/data/090206-3/57.jpg b/bin/data/090206-3/57.jpg new file mode 100644 index 0000000000..51c82dfbd1 Binary files /dev/null and b/bin/data/090206-3/57.jpg differ diff --git a/bin/data/090206-3/58.jpg b/bin/data/090206-3/58.jpg new file mode 100644 index 0000000000..56da410c72 Binary files /dev/null and b/bin/data/090206-3/58.jpg differ diff --git a/bin/data/090206-3/59.jpg b/bin/data/090206-3/59.jpg new file mode 100644 index 0000000000..fcfbc1b8aa Binary files /dev/null and b/bin/data/090206-3/59.jpg differ diff --git a/bin/data/090206-3/6.jpg b/bin/data/090206-3/6.jpg new file mode 100644 index 0000000000..2b8f45ac6d Binary files /dev/null and b/bin/data/090206-3/6.jpg differ diff --git a/bin/data/090206-3/60.jpg b/bin/data/090206-3/60.jpg new file mode 100644 index 0000000000..2b3d58aa55 Binary files /dev/null and b/bin/data/090206-3/60.jpg differ diff --git a/bin/data/090206-3/61.jpg b/bin/data/090206-3/61.jpg new file mode 100644 index 0000000000..d134c3ad20 Binary files /dev/null and b/bin/data/090206-3/61.jpg differ diff --git a/bin/data/090206-3/62.jpg b/bin/data/090206-3/62.jpg new file mode 100644 index 0000000000..d2abdab042 Binary files /dev/null and b/bin/data/090206-3/62.jpg differ diff --git a/bin/data/090206-3/63.jpg b/bin/data/090206-3/63.jpg new file mode 100644 index 0000000000..5d1ea81835 Binary files /dev/null and b/bin/data/090206-3/63.jpg differ diff --git a/bin/data/090206-3/64.jpg b/bin/data/090206-3/64.jpg new file mode 100644 index 0000000000..e6ac8fc9f8 Binary files /dev/null and b/bin/data/090206-3/64.jpg differ diff --git a/bin/data/090206-3/65.jpg b/bin/data/090206-3/65.jpg new file mode 100644 index 0000000000..ced4c5424c Binary files /dev/null and b/bin/data/090206-3/65.jpg differ diff --git a/bin/data/090206-3/66.jpg b/bin/data/090206-3/66.jpg new file mode 100644 index 0000000000..0778211307 Binary files /dev/null and b/bin/data/090206-3/66.jpg differ diff --git a/bin/data/090206-3/67.jpg b/bin/data/090206-3/67.jpg new file mode 100644 index 0000000000..c9adab2df7 Binary files /dev/null and b/bin/data/090206-3/67.jpg differ diff --git a/bin/data/090206-3/68.jpg b/bin/data/090206-3/68.jpg new file mode 100644 index 0000000000..739f52c512 Binary files /dev/null and b/bin/data/090206-3/68.jpg differ diff --git a/bin/data/090206-3/69.jpg b/bin/data/090206-3/69.jpg new file mode 100644 index 0000000000..2e5929ef4a Binary files /dev/null and b/bin/data/090206-3/69.jpg differ diff --git a/bin/data/090206-3/7.jpg b/bin/data/090206-3/7.jpg new file mode 100644 index 0000000000..5d44e40fc2 Binary files /dev/null and b/bin/data/090206-3/7.jpg differ diff --git a/bin/data/090206-3/70.jpg b/bin/data/090206-3/70.jpg new file mode 100644 index 0000000000..d613b8688a Binary files /dev/null and b/bin/data/090206-3/70.jpg differ diff --git a/bin/data/090206-3/71.jpg b/bin/data/090206-3/71.jpg new file mode 100644 index 0000000000..84d1c62814 Binary files /dev/null and b/bin/data/090206-3/71.jpg differ diff --git a/bin/data/090206-3/72.jpg b/bin/data/090206-3/72.jpg new file mode 100644 index 0000000000..6c7e653767 Binary files /dev/null and b/bin/data/090206-3/72.jpg differ diff --git a/bin/data/090206-3/73.jpg b/bin/data/090206-3/73.jpg new file mode 100644 index 0000000000..dba9cbf276 Binary files /dev/null and b/bin/data/090206-3/73.jpg differ diff --git a/bin/data/090206-3/74.jpg b/bin/data/090206-3/74.jpg new file mode 100644 index 0000000000..f242713a2a Binary files /dev/null and b/bin/data/090206-3/74.jpg differ diff --git a/bin/data/090206-3/75.jpg b/bin/data/090206-3/75.jpg new file mode 100644 index 0000000000..dd36a8093b Binary files /dev/null and b/bin/data/090206-3/75.jpg differ diff --git a/bin/data/090206-3/76.jpg b/bin/data/090206-3/76.jpg new file mode 100644 index 0000000000..ad71c76294 Binary files /dev/null and b/bin/data/090206-3/76.jpg differ diff --git a/bin/data/090206-3/77.jpg b/bin/data/090206-3/77.jpg new file mode 100644 index 0000000000..16ee7a7159 Binary files /dev/null and b/bin/data/090206-3/77.jpg differ diff --git a/bin/data/090206-3/78.jpg b/bin/data/090206-3/78.jpg new file mode 100644 index 0000000000..5e4db28ca7 Binary files /dev/null and b/bin/data/090206-3/78.jpg differ diff --git a/bin/data/090206-3/79.jpg b/bin/data/090206-3/79.jpg new file mode 100644 index 0000000000..a3ca16fa54 Binary files /dev/null and b/bin/data/090206-3/79.jpg differ diff --git a/bin/data/090206-3/8.jpg b/bin/data/090206-3/8.jpg new file mode 100644 index 0000000000..1259f25bc1 Binary files /dev/null and b/bin/data/090206-3/8.jpg differ diff --git a/bin/data/090206-3/80.jpg b/bin/data/090206-3/80.jpg new file mode 100644 index 0000000000..132bdf613b Binary files /dev/null and b/bin/data/090206-3/80.jpg differ diff --git a/bin/data/090206-3/81.jpg b/bin/data/090206-3/81.jpg new file mode 100644 index 0000000000..2e04fa7689 Binary files /dev/null and b/bin/data/090206-3/81.jpg differ diff --git a/bin/data/090206-3/82.jpg b/bin/data/090206-3/82.jpg new file mode 100644 index 0000000000..02f98724be Binary files /dev/null and b/bin/data/090206-3/82.jpg differ diff --git a/bin/data/090206-3/83.jpg b/bin/data/090206-3/83.jpg new file mode 100644 index 0000000000..eccfebc46d Binary files /dev/null and b/bin/data/090206-3/83.jpg differ diff --git a/bin/data/090206-3/84.jpg b/bin/data/090206-3/84.jpg new file mode 100644 index 0000000000..4bb6e5dca3 Binary files /dev/null and b/bin/data/090206-3/84.jpg differ diff --git a/bin/data/090206-3/9.jpg b/bin/data/090206-3/9.jpg new file mode 100644 index 0000000000..afe1e5ab76 Binary files /dev/null and b/bin/data/090206-3/9.jpg differ diff --git a/bin/data/090206-3_GT.bmp b/bin/data/090206-3_GT.bmp new file mode 100644 index 0000000000..2bd0cbee1c Binary files /dev/null and b/bin/data/090206-3_GT.bmp differ diff --git a/bin/data/BoxImgs/1.png b/bin/data/BoxImgs/1.png new file mode 100644 index 0000000000..6f01082f79 Binary files /dev/null and b/bin/data/BoxImgs/1.png differ diff --git a/bin/data/BoxImgs/2.png b/bin/data/BoxImgs/2.png new file mode 100644 index 0000000000..cff246a3fe Binary files /dev/null and b/bin/data/BoxImgs/2.png differ diff --git a/bin/data/README b/bin/data/README new file mode 100644 index 0000000000..2ef532b81f --- /dev/null +++ b/bin/data/README @@ -0,0 +1 @@ +This directory contains some data used when testing the lib. \ No newline at end of file diff --git a/bin/data/samples.zip b/bin/data/samples.zip new file mode 100644 index 0000000000..63b960fc7d Binary files /dev/null and b/bin/data/samples.zip differ diff --git a/bin/libcppunit-1-12-1.dll b/bin/libcppunit-1-12-1.dll new file mode 100644 index 0000000000..48afdc5cf6 Binary files /dev/null and b/bin/libcppunit-1-12-1.dll differ diff --git a/bin/libsqlite3-0.dll b/bin/libsqlite3-0.dll new file mode 100644 index 0000000000..2f816d9c17 Binary files /dev/null and b/bin/libsqlite3-0.dll differ diff --git a/bin/sqlite3.exe b/bin/sqlite3.exe new file mode 100644 index 0000000000..31230945ff Binary files /dev/null and b/bin/sqlite3.exe differ diff --git a/build/.empty b/build/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cmake_modules/FindCppUnit.cmake b/cmake_modules/FindCppUnit.cmake new file mode 100644 index 0000000000..0738f9a4ca --- /dev/null +++ b/cmake_modules/FindCppUnit.cmake @@ -0,0 +1,30 @@ +# - Find CppUnit +# This module finds an installed CppUnit package. +# +# It sets the following variables: +# CPPUNIT_FOUND - Set to false, or undefined, if CppUnit isn't found. +# CPPUNIT_INCLUDE_DIR - The CppUnit include directory. +# CPPUNIT_LIBRARY - The CppUnit library to link against. +# CPPUNIT_DEFINITIONS - The CppUnit definitions. + +FIND_PATH(CPPUNIT_INCLUDE_DIR cppunit/Test.h) + +FIND_LIBRARY(CPPUNIT_LIBRARY NAMES cppunit_dll cppunit) + +IF (CPPUNIT_INCLUDE_DIR AND CPPUNIT_LIBRARY) + SET(CPPUNIT_FOUND TRUE) + SET(CPPUNIT_DEFINITIONS "-DCPPUNIT_DLL") +ENDIF (CPPUNIT_INCLUDE_DIR AND CPPUNIT_LIBRARY) + +IF (CPPUNIT_FOUND) + # show which CppUnit was found only if not quiet + IF (NOT CppUnit_FIND_QUIETLY) + MESSAGE(STATUS "Found CppUnit") + ENDIF (NOT CppUnit_FIND_QUIETLY) +ELSE (CPPUNIT_FOUND) + # fatal error if CppUnit is required but not found + IF (CppUnit_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find CppUnit") + ENDIF (CppUnit_FIND_REQUIRED) +ENDIF (CPPUNIT_FOUND) + diff --git a/cmake_modules/FindOpenCV.cmake b/cmake_modules/FindOpenCV.cmake new file mode 100644 index 0000000000..aee9244349 --- /dev/null +++ b/cmake_modules/FindOpenCV.cmake @@ -0,0 +1,323 @@ +########################################################### +# Find OpenCV Library +# See http://sourceforge.net/projects/opencvlibrary/ +#---------------------------------------------------------- +# +## 1: Setup: +# The following variables are optionally searched for defaults +# OpenCV_DIR: Base directory of OpenCv tree to use. +# +## 2: Variable +# The following are set after configuration is done: +# +# OpenCV_FOUND +# OpenCV_LIBS +# OpenCV_INCLUDE_DIR +# OpenCV_VERSION (OpenCV_VERSION_MAJOR, OpenCV_VERSION_MINOR, OpenCV_VERSION_PATCH) +# +# +# Deprecated variable are used to maintain backward compatibility with +# the script of Jan Woetzel (2006/09): www.mip.informatik.uni-kiel.de/~jw +# OpenCV_INCLUDE_DIRS +# OpenCV_LIBRARIES +# OpenCV_LINK_DIRECTORIES +# +## 3: Version +# +# 2010/04/07 Benoit Rat, Correct a bug when OpenCVConfig.cmake is not found. +# 2010/03/24 Benoit Rat, Add compatibility for when OpenCVConfig.cmake is not found. +# 2010/03/22 Benoit Rat, Creation of the script. +# +# +# tested with: +# - OpenCV 2.1: MinGW, MSVC2008 +# - OpenCV 2.0: MinGW, MSVC2008, GCC4 +# +# +## 4: Licence: +# +# LGPL 2.1 : GNU Lesser General Public License Usage +# Alternatively, this file may be used under the terms of the GNU Lesser + +# General Public License version 2.1 as published by the Free Software +# Foundation and appearing in the file LICENSE.LGPL included in the +# packaging of this file. Please review the following information to +# ensure the GNU Lesser General Public License version 2.1 requirements +# will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +# +#---------------------------------------------------------- + +# typical root dirs of installations, exactly one of them is used +SET (OpenCV_POSSIBLE_ROOT_DIRS + "$ENV{OpenCV_DIR}" + /usr/local + /usr/opt + /usr + ) + +# Add ROS OpenCV directory if ROS is installed +FIND_PROGRAM(ROSPACK_EXEC NAME rospack PATHS) +IF(ROSPACK_EXEC) + MESSAGE(STATUS "Found rospack executable : ${ROSPACK_EXEC}") + EXECUTE_PROCESS(COMMAND ${ROSPACK_EXEC} find opencv2 + OUTPUT_VARIABLE OPENCV_ROS_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY "./" + ) + IF(OPENCV_ROS_PATH) + MESSAGE(STATUS "Found OpenCV ROS pkg : ${OPENCV_ROS_PATH}") + SET(OpenCV_POSSIBLE_ROOT_DIRS + ${OPENCV_ROS_PATH}/opencv + ${OpenCV_POSSIBLE_ROOT_DIRS} + ) + ENDIF(OPENCV_ROS_PATH) +ENDIF(ROSPACK_EXEC) + +find_path(OpenCV_DIR "OpenCVConfig.cmake" HINTS ${OpenCV_POSSIBLE_ROOT_DIRS} DOC "Root directory of OpenCV") +if(NOT EXISTS "${OpenCV_DIR}") + if(NOT WIN32) + find_path(OpenCV_DIR "share/opencv/OpenCVConfig.cmake" HINTS ${OpenCV_POSSIBLE_ROOT_DIRS} DOC "Root directory of OpenCV") + if(NOT OpenCV_DIR) + include(FindPkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(OPENCV_PKGCONF opencv) + set(OpenCV_DIR ${OPENCV_PKGCONF_PREFIX}) + endif(PKG_CONFIG_FOUND) + endif(NOT OpenCV_DIR) + else() + set(OpenCV_DIR "$ENV{OpenCV_DIR}") + endif () +endif(NOT EXISTS "${OpenCV_DIR}") + +##==================================================== +## Find OpenCV libraries +##---------------------------------------------------- +if(EXISTS "${OpenCV_DIR}") + + set(OpenCV_configScript "${OpenCV_DIR}/OpenCVConfig.cmake") + if(NOT EXISTS "${OpenCV_configScript}") + set(OpenCV_configScript "${OpenCV_DIR}/share/opencv/OpenCVConfig.cmake") + endif(NOT EXISTS "${OpenCV_configScript}") + + + #When its possible to use the Config script use it. + if(EXISTS "${OpenCV_configScript}") + + MESSAGE(STATUS "OpenCV: using configuration file ${OpenCV_configScript}") + + ## Include the standard CMake script + include("${OpenCV_configScript}") + + ## Search for a specific version + if(WIN32 OR NOT PKG_CONFIG_FOUND) + set(CVLIB_SUFFIX "${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}") + endif(WIN32 OR NOT PKG_CONFIG_FOUND) + + ##Boris:TODO: check for correct OpenCV version in pkg-config case !!! + + + #Otherwise it try to guess it. + else(EXISTS "${OpenCV_configScript}") + + + find_path(OpenCV_INCLUDE_DIR "cv.h" PATHS "${OpenCV_DIR}" PATH_SUFFIXES "include" "include/opencv" DOC "") + if(EXISTS ${OpenCV_INCLUDE_DIR}) + include_directories(${OpenCV_INCLUDE_DIR}) + endif(EXISTS ${OpenCV_INCLUDE_DIR}) + + if(NOT EXISTS ${VERSION_FILE_DIR}) #may alreay be in cache + + #Find OpenCV version by looking at cvver.h or core/version.hpp + find_path(VERSION_FILE_DIR "version.hpp" PATHS "${OpenCV_DIR}" PATH_SUFFIXES "include" "include/opencv" "include/opencv2" "include/opencv2/core" DOC "") + if(NOT EXISTS ${VERSION_FILE_DIR}) + find_path(VERSION_FILE_DIR "cvver.h" PATHS "${OpenCV_DIR}" PATH_SUFFIXES "include" "include/opencv" DOC "") + if(NOT EXISTS ${VERSION_FILE_DIR}) + message(FATAL_ERROR "OpenCV version file not found") + else(NOT EXISTS ${VERSION_FILE_DIR}) + set(VERSION_FILE ${VERSION_FILE_DIR}/cvver.h) + endif(NOT EXISTS ${VERSION_FILE_DIR}) + + else(NOT EXISTS ${VERSION_FILE_DIR}) + set(VERSION_FILE ${VERSION_FILE_DIR}/version.hpp) + + endif(NOT EXISTS ${VERSION_FILE_DIR}) + + #file(STRINGS ${OpenCV_INCLUDE_DIR}/cvver.h OpenCV_VERSIONS_TMP REGEX "^#define CV_[A-Z]+_VERSION[ \t]+[0-9]+$") + file(STRINGS ${VERSION_FILE} OpenCV_VERSIONS_TMP REGEX "^#define CV_[A-Z]+_VERSION[ \t]+[0-9]+$") + + string(REGEX REPLACE ".*#define CV_MAJOR_VERSION[ \t]+([0-9]+).*" "\\1" OpenCV_VERSION_MAJOR ${OpenCV_VERSIONS_TMP}) + string(REGEX REPLACE ".*#define CV_MINOR_VERSION[ \t]+([0-9]+).*" "\\1" OpenCV_VERSION_MINOR ${OpenCV_VERSIONS_TMP}) + string(REGEX REPLACE ".*#define CV_SUBMINOR_VERSION[ \t]+([0-9]+).*" "\\1" OpenCV_VERSION_PATCH ${OpenCV_VERSIONS_TMP}) + set(OpenCV_VERSION ${OpenCV_VERSION_MAJOR}.${OpenCV_VERSION_MINOR}.${OpenCV_VERSION_PATCH} CACHE STRING "" FORCE) + + if(WIN32 OR NOT PKG_CONFIG_FOUND) + set(CVLIB_SUFFIX "${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}") + endif(WIN32 OR NOT PKG_CONFIG_FOUND) + + ##Boris:TODO: check for correct OpenCV version in pklg-config case !!! + + + endif(NOT EXISTS ${VERSION_FILE_DIR}) + + if(${OpenCV_VERSION} VERSION_GREATER 2.1.0) + set(OPENCV_LIB_COMPONENTS calib3d contrib core features2d ffmpeg flann gpu highgui imgproc legacy ml objdetect ts video) + + #Add parent directory of ${OpenCV_INCLUDE_DIR} to ${OpenCV_INCLUDE_DIR} itself + #to be able to do both + #include and #include + get_filename_component(PARENT_DIR ${OpenCV_INCLUDE_DIR} PATH) + set(OpenCV_INCLUDE_DIR "${OpenCV_INCLUDE_DIR};${PARENT_DIR}") + + else(${OpenCV_VERSION} VERSION_GREATER 2.1.0) + set(OPENCV_LIB_COMPONENTS cxcore cv ml highgui cvaux) + endif(${OpenCV_VERSION} VERSION_GREATER 2.1.0) + + endif(EXISTS "${OpenCV_configScript}") + + + if(${OpenCV_VERSION} VERSION_GREATER 2.1.0) + + set(CVLIB_SUFFIX ".${OpenCV_VERSION}") + if(WIN32 OR NOT PKG_CONFIG_FOUND) + set(CVLIB_SUFFIX "${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}") + endif(WIN32 OR NOT PKG_CONFIG_FOUND) + + ## Initiate the variable before the loop + set(OpenCV_LIBS "") + set(OpenCV_FOUND_TMP true) + + ## Loop over each components + foreach(__CVLIB ${OPENCV_LIB_COMPONENTS}) + + find_library(OpenCV_${__CVLIB}_LIBRARY_DEBUG NAMES "${__CVLIB}${CVLIB_SUFFIX}d" "lib${__CVLIB}${CVLIB_SUFFIX}d" PATHS "${OpenCV_DIR}/lib" NO_DEFAULT_PATH) + find_library(OpenCV_${__CVLIB}_LIBRARY_RELEASE NAMES "${__CVLIB}${CVLIB_SUFFIX}" "lib${__CVLIB}${CVLIB_SUFFIX}" "opencv_${__CVLIB}${CVLIB_SUFFIX}" "opencv_${__CVLIB}" "${__CVLIB}" PATHS "${OpenCV_DIR}/lib" NO_DEFAULT_PATH) + + #On MacOSX libraries are named: libopencv_${__CVLIB}${CVLIB_SUFFIX}.dylib + #On Linux libraries are named: libopencv_${__CVLIB}.so${CVLIB_SUFFIX} + # but with pkg-config ${OPENCV_LIB_COMPONENTS} are already prefixed with opencv_ ? + + #we add: "opencv_${__CVLIB}${CVLIB_SUFFIX}" for MacOSX + #we add: "${__CVLIB}" for linux (but version is not checked !) + + + #Remove the cache value + set(OpenCV_${__CVLIB}_LIBRARY "" CACHE STRING "" FORCE) + + #both debug/release + if(OpenCV_${__CVLIB}_LIBRARY_DEBUG AND OpenCV_${__CVLIB}_LIBRARY_RELEASE) + set(OpenCV_${__CVLIB}_LIBRARY debug ${OpenCV_${__CVLIB}_LIBRARY_DEBUG} optimized ${OpenCV_${__CVLIB}_LIBRARY_RELEASE} CACHE STRING "" FORCE) + + #only debug + elseif(OpenCV_${__CVLIB}_LIBRARY_DEBUG) + set(OpenCV_${__CVLIB}_LIBRARY ${OpenCV_${__CVLIB}_LIBRARY_DEBUG} CACHE STRING "" FORCE) + + #only release + elseif(OpenCV_${__CVLIB}_LIBRARY_RELEASE) + set(OpenCV_${__CVLIB}_LIBRARY ${OpenCV_${__CVLIB}_LIBRARY_RELEASE} CACHE STRING "" FORCE) + + #no library found + else() + set(OpenCV_FOUND_TMP false) + + endif() + + + #Add to the general list + if(OpenCV_${__CVLIB}_LIBRARY) + set(OpenCV_LIBS ${OpenCV_LIBS} ${OpenCV_${__CVLIB}_LIBRARY}) + endif(OpenCV_${__CVLIB}_LIBRARY) + + endforeach(__CVLIB) + + + set(OpenCV_FOUND ${OpenCV_FOUND_TMP} CACHE BOOL "" FORCE) + + + else(${OpenCV_VERSION} VERSION_GREATER 2.1.0) + + + ## Initiate the variable before the loop + set(OpenCV_LIBS "") + set(OpenCV_FOUND_TMP true) + + ## Loop over each components + foreach(__CVLIB ${OPENCV_LIB_COMPONENTS}) + + find_library(OpenCV_${__CVLIB}_LIBRARY_DEBUG NAMES "${__CVLIB}${CVLIB_SUFFIX}d" "lib${__CVLIB}${CVLIB_SUFFIX}d" PATHS "${OpenCV_DIR}/lib" NO_DEFAULT_PATH) + find_library(OpenCV_${__CVLIB}_LIBRARY_RELEASE NAMES "${__CVLIB}${CVLIB_SUFFIX}" "lib${__CVLIB}${CVLIB_SUFFIX}" PATHS "${OpenCV_DIR}/lib" NO_DEFAULT_PATH) + + + + #Remove the cache value + set(OpenCV_${__CVLIB}_LIBRARY "" CACHE STRING "" FORCE) + + #both debug/release + if(OpenCV_${__CVLIB}_LIBRARY_DEBUG AND OpenCV_${__CVLIB}_LIBRARY_RELEASE) + set(OpenCV_${__CVLIB}_LIBRARY debug ${OpenCV_${__CVLIB}_LIBRARY_DEBUG} optimized ${OpenCV_${__CVLIB}_LIBRARY_RELEASE} CACHE STRING "" FORCE) + + #only debug + elseif(OpenCV_${__CVLIB}_LIBRARY_DEBUG) + set(OpenCV_${__CVLIB}_LIBRARY ${OpenCV_${__CVLIB}_LIBRARY_DEBUG} CACHE STRING "" FORCE) + + #only release + elseif(OpenCV_${__CVLIB}_LIBRARY_RELEASE) + set(OpenCV_${__CVLIB}_LIBRARY ${OpenCV_${__CVLIB}_LIBRARY_RELEASE} CACHE STRING "" FORCE) + + #no library found + else() + set(OpenCV_FOUND_TMP false) + + endif() + + #Add to the general list + if(OpenCV_${__CVLIB}_LIBRARY) + set(OpenCV_LIBS ${OpenCV_LIBS} ${OpenCV_${__CVLIB}_LIBRARY}) + endif(OpenCV_${__CVLIB}_LIBRARY) + + + endforeach(__CVLIB) + + + set(OpenCV_FOUND ${OpenCV_FOUND_TMP} CACHE BOOL "" FORCE) + + endif(${OpenCV_VERSION} VERSION_GREATER 2.1.0) + + +else(EXISTS "${OpenCV_DIR}") + set(ERR_MSG "Please specify OpenCV directory using OpenCV_DIR env. variable") +endif(EXISTS "${OpenCV_DIR}") +##==================================================== + +##==================================================== +## Print message +##---------------------------------------------------- +if(NOT OpenCV_FOUND) + # make FIND_PACKAGE friendly + if(NOT OpenCV_FIND_QUIETLY) + if(OpenCV_FIND_REQUIRED) + message(FATAL_ERROR "OpenCV required but some headers or libs not found. ${ERR_MSG}") + else(OpenCV_FIND_REQUIRED) + message(STATUS "WARNING: OpenCV was not found. ${ERR_MSG}") + endif(OpenCV_FIND_REQUIRED) + endif(NOT OpenCV_FIND_QUIETLY) +else() + if(NOT OpenCV_FIND_QUIETLY) + message(STATUS "Found OpenCV ${OpenCV_VERSION}") + endif(NOT OpenCV_FIND_QUIETLY) +endif() +##==================================================== + + +##==================================================== +## Backward compatibility +##---------------------------------------------------- +if(OpenCV_FOUND) + set(OpenCV_INCLUDE_DIRS "${OpenCV_INCLUDE_DIR}") + #option(OpenCV_BACKWARD_COMPA "Add some variable to make this script compatible with the other version of FindOpenCV.cmake" false) + #if(OpenCV_BACKWARD_COMPA) + # find_path(OpenCV_INCLUDE_DIRS "cv.h" PATHS "${OpenCV_DIR}" PATH_SUFFIXES "include" "include/opencv" DOC "Include directory") + # find_path(OpenCV_INCLUDE_DIR "cv.h" PATHS "${OpenCV_DIR}" PATH_SUFFIXES "include" "include/opencv" DOC "Include directory") + # set(OpenCV_LIBRARIES "${OpenCV_LIBS}" CACHE STRING "" FORCE) + #endif(OpenCV_BACKWARD_COMPA) +endif(OpenCV_FOUND) +##==================================================== diff --git a/cmake_modules/FindPthreads.cmake b/cmake_modules/FindPthreads.cmake new file mode 100644 index 0000000000..ce591df6be --- /dev/null +++ b/cmake_modules/FindPthreads.cmake @@ -0,0 +1,98 @@ +# - Find the Pthreads library +# This module searches for the Pthreads library (including the +# pthreads-win32 port). +# +# This module defines these variables: +# +# PTHREADS_FOUND +# True if the Pthreads library was found +# PTHREADS_LIBRARY +# The location of the Pthreads library +# PTHREADS_INCLUDE_DIR +# The include path of the Pthreads library +# PTHREADS_DEFINITIONS +# Preprocessor definitions to define +# +# This module responds to the PTHREADS_EXCEPTION_SCHEME +# variable on Win32 to allow the user to control the +# library linked against. The Pthreads-win32 port +# provides the ability to link against a version of the +# library with exception handling. IT IS NOT RECOMMENDED +# THAT YOU USE THIS because most POSIX thread implementations +# do not support stack unwinding. +# +# PTHREADS_EXCEPTION_SCHEME +# C = no exceptions (default) +# (NOTE: This is the default scheme on most POSIX thread +# implementations and what you should probably be using) +# CE = C++ Exception Handling +# SE = Structure Exception Handling (MSVC only) +# + +# +# Define a default exception scheme to link against +# and validate user choice. +# +IF(NOT DEFINED PTHREADS_EXCEPTION_SCHEME) + # Assign default if needed + SET(PTHREADS_EXCEPTION_SCHEME "C") +ELSE(NOT DEFINED PTHREADS_EXCEPTION_SCHEME) + # Validate + IF(NOT PTHREADS_EXCEPTION_SCHEME STREQUAL "C" AND + NOT PTHREADS_EXCEPTION_SCHEME STREQUAL "CE" AND + NOT PTHREADS_EXCEPTION_SCHEME STREQUAL "SE") + + MESSAGE(FATAL_ERROR "See documentation for FindPthreads.cmake, only C, CE, and SE modes are allowed") + + ENDIF(NOT PTHREADS_EXCEPTION_SCHEME STREQUAL "C" AND + NOT PTHREADS_EXCEPTION_SCHEME STREQUAL "CE" AND + NOT PTHREADS_EXCEPTION_SCHEME STREQUAL "SE") + + IF(NOT MSVC AND PTHREADS_EXCEPTION_SCHEME STREQUAL "SE") + MESSAGE(FATAL_ERROR "Structured Exception Handling is only allowed for MSVC") + ENDIF(NOT MSVC AND PTHREADS_EXCEPTION_SCHEME STREQUAL "SE") + +ENDIF(NOT DEFINED PTHREADS_EXCEPTION_SCHEME) + +# +# Find the header file +# +FIND_PATH(PTHREADS_INCLUDE_DIR pthread.h) + +# +# Find the library +# +SET(names) +IF(MSVC) + SET(names + pthreadV${PTHREADS_EXCEPTION_SCHEME}2 + pthread + ) +ELSEIF(MINGW) + SET(names + pthreadG${PTHREADS_EXCEPTION_SCHEME}2 + pthread + ) +ELSE(MSVC) # Unix / Cygwin / Apple + SET(names pthread) +ENDIF(MSVC) + +FIND_LIBRARY(PTHREADS_LIBRARY ${names} + DOC "The Portable Threads Library") + +IF(PTHREADS_INCLUDE_DIR AND PTHREADS_LIBRARY) + SET(PTHREADS_FOUND true) + SET(PTHREADS_DEFINITIONS -DHAVE_PTHREAD_H) + SET(PTHREADS_INCLUDE_DIRS ${PTHREADS_INCLUDE_DIR}) + SET(PTHREADS_LIBRARIES ${PTHREADS_LIBRARY}) +ENDIF(PTHREADS_INCLUDE_DIR AND PTHREADS_LIBRARY) + +IF(PTHREADS_FOUND) + IF(NOT PTHREADS_FIND_QUIETLY) + MESSAGE(STATUS "Found Pthreads: ${PTHREADS_LIBRARY}") + ENDIF(NOT PTHREADS_FIND_QUIETLY) +ELSE(PTHREADS_FOUND) + IF(PTHREADS_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find the Pthreads Library") + ENDIF(PTHREADS_FIND_REQUIRED) +ENDIF(PTHREADS_FOUND) diff --git a/cmake_modules/FindROS.cmake b/cmake_modules/FindROS.cmake new file mode 100644 index 0000000000..4846607fed --- /dev/null +++ b/cmake_modules/FindROS.cmake @@ -0,0 +1,25 @@ +# - Find ROS +# This module finds an installed ROS package. +# +# It sets the following variables: +# ROS_FOUND - Set to false, or undefined, if ROS isn't found. + +IF(UNIX) + FIND_PROGRAM(ROS_EXEC NAME rosrun PATHS) + IF(ROS_EXEC) + SET(ROS_FOUND TRUE) + ENDIF(ROS_EXEC) +ENDIF(UNIX) + +IF (ROS_FOUND) + # show which ROS was found only if not quiet + IF (NOT ROS_FIND_QUIETLY) + MESSAGE(STATUS "Found ROS") + ENDIF (NOT ROS_FIND_QUIETLY) +ELSE (ROS_FOUND) + # fatal error if ROS is required but not found + IF (ROS_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find ROS") + ENDIF (ROS_FIND_REQUIRED) +ENDIF (ROS_FOUND) + diff --git a/cmake_modules/FindSqlite3.cmake b/cmake_modules/FindSqlite3.cmake new file mode 100644 index 0000000000..e8642a742d --- /dev/null +++ b/cmake_modules/FindSqlite3.cmake @@ -0,0 +1,47 @@ +# - Find Sqlite3 +# This module finds an installed Sqlite3 package. +# +# It sets the following variables: +# SQLITE3_FOUND - Set to false, or undefined, if Sqlite3 isn't found. +# SQLITE3_INCLUDE_DIR - The Sqlite3 include directory. +# SQLITE3_LIBRARY - The Sqlite3 library to link against. + +SET(SQLITE3_VERSION_REQUIRED "3.6.0") + +IF(UNIX) + FIND_PROGRAM(SQLITE3_EXEC NAME sqlite3 PATHS) + IF(SQLITE3_EXEC) + MESSAGE(STATUS "Found Sqlite3 executable : ${SQLITE3_EXEC}") + EXECUTE_PROCESS(COMMAND ${SQLITE3_EXEC} --version + OUTPUT_VARIABLE SQLITE3_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY "./" + ) + IF(SQLITE3_VERSION VERSION_LESS SQLITE3_VERSION_REQUIRED) + MESSAGE(FATAL_ERROR "Sqlite ${SQLITE3_VERSION} found, but version ${SQLITE3_VERSION_REQUIRED} minimum is required") + ENDIF(SQLITE3_VERSION VERSION_LESS SQLITE3_VERSION_REQUIRED) + ELSE(SQLITE3_EXEC) + MESSAGE(FATAL_ERROR "Could not find Sqlite3 executable") + ENDIF(SQLITE3_EXEC) +ENDIF(UNIX) + +FIND_PATH(SQLITE3_INCLUDE_DIR sqlite3.h PATH_SUFFIXES sqlite3) + +FIND_LIBRARY(SQLITE3_LIBRARY NAMES sqlite3.dll sqlite3) + +IF (SQLITE3_INCLUDE_DIR AND SQLITE3_LIBRARY) + SET(SQLITE3_FOUND TRUE) +ENDIF (SQLITE3_INCLUDE_DIR AND SQLITE3_LIBRARY) + +IF (SQLITE3_FOUND) + # show which Sqlite3 was found only if not quiet + IF (NOT Sqlite3_FIND_QUIETLY) + MESSAGE(STATUS "Found Sqlite3") + ENDIF (NOT Sqlite3_FIND_QUIETLY) +ELSE (SQLITE3_FOUND) + # fatal error if Sqlite3 is required but not found + IF (Sqlite3_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find Sqlite3") + ENDIF (Sqlite3_FIND_REQUIRED) +ENDIF (SQLITE3_FOUND) + diff --git a/cmake_modules/FindUtiLite.cmake b/cmake_modules/FindUtiLite.cmake new file mode 100644 index 0000000000..c510aab0f0 --- /dev/null +++ b/cmake_modules/FindUtiLite.cmake @@ -0,0 +1,62 @@ +# - Find UTILITE +# This module finds an installed UTILITE package. +# +# It sets the following variables: +# UTILITE_FOUND - Set to false, or undefined, if UTILITE isn't found. +# UTILITE_INCLUDE_DIR - The UTILITE include directory. +# UTILITE_LIBRARY - The UTILITE library to link against. +# URESOURCEGENERATOR_EXEC - The resource generator tool executable +# +# + +SET(UTILITE_VERSION_REQUIRED 0.2.11) + +FIND_PROGRAM(URESOURCEGENERATOR_EXEC NAME uresourcegenerator PATHS) +IF(URESOURCEGENERATOR_EXEC) + EXECUTE_PROCESS(COMMAND ${URESOURCEGENERATOR_EXEC} -v + OUTPUT_VARIABLE UTILITE_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY "./" + ) + + IF(UTILITE_VERSION VERSION_LESS UTILITE_VERSION_REQUIRED) + IF(UtiLite_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Your version of UtiLite is too old (${UTILITE_VERSION}), UtiLite ${UTILITE_VERSION_REQUIRED} is required.") + ENDIF(UtiLite_FIND_REQUIRED) + ENDIF(UTILITE_VERSION VERSION_LESS UTILITE_VERSION_REQUIRED) + + IF(WIN32) + FIND_PATH(UTILITE_INCLUDE_DIR + utilite/UEventsManager.h + PATH_SUFFIXES "../include") + + FIND_LIBRARY(UTILITE_LIBRARY NAMES utilite + PATH_SUFFIXES "../lib") + + ELSE() + FIND_PATH(UTILITE_INCLUDE_DIR + utilite/UEventsManager.h) + + FIND_LIBRARY(UTILITE_LIBRARY NAMES utilite) + ENDIF() + + IF (UTILITE_INCLUDE_DIR AND UTILITE_LIBRARY) + SET(UTILITE_FOUND TRUE) + ENDIF (UTILITE_INCLUDE_DIR AND UTILITE_LIBRARY) +ENDIF(URESOURCEGENERATOR_EXEC) + +IF (UTILITE_FOUND) + # show which UTILITE was found only if not quiet + IF (NOT UtiLite_FIND_QUIETLY) + MESSAGE(STATUS "Found UtiLite ${UTILITE_VERSION}") + ENDIF (NOT UtiLite_FIND_QUIETLY) + IF (NOT URESOURCEGENERATOR_EXEC) + MESSAGE(STATUS "uresourcegenerator was not found") + ENDIF (NOT URESOURCEGENERATOR_EXEC) +ELSE () + # fatal error if UTILITE is required but not found + IF (UtiLite_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find UtiLite. Verify your PATH if it is already installed or download it at http://utilite.googlecode.com") + ENDIF (UtiLite_FIND_REQUIRED) +ENDIF () + diff --git a/cmake_uninstall.cmake.in b/cmake_uninstall.cmake.in new file mode 100644 index 0000000000..14e601019d --- /dev/null +++ b/cmake_uninstall.cmake.in @@ -0,0 +1,27 @@ +# ----------------------------------------------- +# File that provides "make uninstall" target +# We use the file 'install_manifest.txt' +# ----------------------------------------------- +IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") +ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +STRING(REGEX REPLACE "\n" ";" files "${files}") +FOREACH(file ${files}) + MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") + IF(EXISTS "$ENV{DESTDIR}${file}") + EXEC_PROGRAM( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + IF(NOT "${rm_retval}" STREQUAL 0) + MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") + ENDIF(NOT "${rm_retval}" STREQUAL 0) + ELSE(EXISTS "$ENV{DESTDIR}${file}") + MESSAGE(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") + ENDIF(EXISTS "$ENV{DESTDIR}${file}") +ENDFOREACH(file) + + diff --git a/corelib/CMakeLists.txt b/corelib/CMakeLists.txt new file mode 100644 index 0000000000..dc2be7559b --- /dev/null +++ b/corelib/CMakeLists.txt @@ -0,0 +1,16 @@ +ADD_SUBDIRECTORY( src ) +ADD_SUBDIRECTORY( ConsoleApp ) +ADD_SUBDIRECTORY( ImagesJoiner ) +ADD_SUBDIRECTORY( WebcamCapture ) + +IF(QT4_FOUND AND QT_QTCORE_FOUND AND QT_QTGUI_FOUND) + ADD_SUBDIRECTORY( DatabaseViewer ) +ELSE() + MESSAGE(STATUS "[WARNING] Qt4 not found, the databaseViewer program will not be built...") +ENDIF() + +IF(CPPUNIT_FOUND) + ADD_SUBDIRECTORY( tests ) +ELSE(CPPUNIT_FOUND) + MESSAGE(STATUS "CppUnit is not found, tests for the ${PROJECT_NAME} project won't be compiled...") +ENDIF(CPPUNIT_FOUND) diff --git a/corelib/ConsoleApp/CMakeLists.txt b/corelib/ConsoleApp/CMakeLists.txt new file mode 100644 index 0000000000..06dbdb2b53 --- /dev/null +++ b/corelib/ConsoleApp/CMakeLists.txt @@ -0,0 +1,33 @@ + +SET(SRC_FILES + main.cpp +) + +SET(INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR} + ${UTILITE_INCLUDE_DIR} + ${OpenCV_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/../include +) + +SET(LIBRARIES + ${UTILITE_LIBRARY} + ${OpenCV_LIBRARIES} +) + +# Make sure the compiler can find include files from our library. +INCLUDE_DIRECTORIES(${INCLUDE_DIRS}) + +# Add binary called "consoleApp" that is built from the source file "main.cpp". +# The extension is automatically found. +ADD_EXECUTABLE(consoleApp ${SRC_FILES}) +TARGET_LINK_LIBRARIES(consoleApp corelib ${LIBRARIES}) + +SET_TARGET_PROPERTIES( consoleApp + PROPERTIES OUTPUT_NAME ${PROJECT_PREFIX}-console) + +INSTALL(TARGETS consoleApp + RUNTIME DESTINATION bin COMPONENT runtime + LIBRARY DESTINATION lib COMPONENT devel + ARCHIVE DESTINATION lib COMPONENT devel) + diff --git a/corelib/ConsoleApp/main.cpp b/corelib/ConsoleApp/main.cpp new file mode 100644 index 0000000000..0082cf274b --- /dev/null +++ b/corelib/ConsoleApp/main.cpp @@ -0,0 +1,560 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include +#include +#include "rtabmap/core/Rtabmap.h" +#include "rtabmap/core/Camera.h" +#include "rtabmap/core/SMState.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace rtabmap; + +#define GENERATED_GT_NAME "GroundTruth_generated.txt" + +void showUsage() +{ + printf("\nUsage:\n" + "rtabmap-console [options] \"path\"\n" + " path For images, use the directory path. For videos, use full\n " + " path name\n" + "Options:\n" + " -t #.## Time threshold (seconds)\n" + " -rate #.## Acquisition time (seconds)\n" + " -rateHz #.## Acquisition rate (Hz), for convenience\n" + " -repeat # Repeat the process on the data set # times (minimum of 1)\n" + " -createGT # Generate a ground truth file of dim # (>0, must match the size\n" + " of the data set)\n" + " -image_width # Force an image width (Default 0: original size used).\n" + " The height must be also specified if changed.\n" + " -image_height # Force an image height (Default 0: original size used)\n" + " The height must be also specified if changed.\n" + " -\"parameter name\" \"value\" Overwrite a specific RTAB-Map's parameter :\n" + " -SURF/HessianThreshold 150\n" + " For parameters in table format, add ',' between values :\n" + " -Kp/RoiRatios 0,0,0.1,0\n" + " Default parameters can be found in ~/.rtabmap/rtabmap.ini\n" + " -default_params Show default RTAB-Map's parameters (WARNING : \n" + " parameters from rtabmap.ini (if exists) overwrite the default \n" + " ones shown here)\n" + " -debug Set Log level to Debug (Default Error)\n" + " -info Set Log level to Info (Default Error)\n" + " -warn Set Log level to Warning (Default Error)\n" + " -exit_warn Set exit level to Warning (Default Fatal)\n" + " -exit_error Set exit level to Error (Default Fatal)\n" + " -v Get version of RTAB-Map\n"); + exit(1); +} + +// catch ctrl-c +bool g_forever = true; +void sighandler(int sig) +{ + printf("\nSignal %d caught...\n", sig); + g_forever = false; +} + +int main(int argc, char * argv[]) +{ + signal(SIGABRT, &sighandler); + signal(SIGTERM, &sighandler); + signal(SIGINT, &sighandler); + + /*for(int i=0; ifirst.c_str(), iter->second.c_str()); + } + exit(0); + } + printf("\n"); + + std::string path; + float timeThreshold = 0.0; + float rate = 0.0; + int loopDataset = 0; + int repeat = 0; + int createGT = 0; + int imageWidth = 0; + int imageHeight = 0; + ParametersMap pm; + ULogger::Level logLevel = ULogger::kError; + ULogger::Level exitLevel = ULogger::kFatal; + + for(int i=1; i iterationMeanTime; + + Camera * camera = 0; + if(UDirectory::exists(path)) + { + camera = new CameraImages(path, 1, false, 0.0f, false, imageWidth, imageHeight); + } + else + { + camera = new CameraVideo(path, 0.0f, false, imageWidth, imageHeight); + } + + if(!camera || !camera->init()) + { + printf("Camera init failed, using path \"%s\"\n", path.c_str()); + exit(1); + } + + + CvMat * groundTruthMat = 0; + if(createGT) + { + printf("Creating the ground truth matrix...%dx%d\n", createGT, createGT); + groundTruthMat = cvCreateMat(createGT, createGT, CV_32FC1); + } + + + // Create tasks + Rtabmap * rtabmap = new Rtabmap(); + rtabmap->init(); + rtabmap->setMaxTimeAllowed(timeThreshold); // in sec + + //ULogger::setType(ULogger::kTypeConsole); + ULogger::setType(ULogger::kTypeFile, rtabmap->getWorkingDir()+"/LogConsole.txt", false); + ULogger::setBuffered(true); + ULogger::setLevel(logLevel); + ULogger::setExitLevel(exitLevel); + + // Disable statistics (we don't need them) + pm.insert(ParametersPair(Parameters::kRtabmapPublishStats(), uBool2str(false))); + rtabmap->init(pm); + + + + + printf("Avpd init time = %fs\n", timer.ticks()); + + // Start thread's task + IplImage * image = 0; + int loopClosureId; + int count = 0; + int countLoopDetected=0; + + printf("\nParameters : \n"); + printf(" Data set : %s\n", path.c_str()); + printf(" Time threshold = %1.2f\n", timeThreshold); + printf(" Image rate = %1.2f s (%1.2f Hz)\n", rate, 1/rate); + printf(" Repeating dataset = %s\n", repeat?"true":"false"); + printf(" Camera width=%d, height=%d (0 is default)\n", imageWidth, imageHeight); + printf(" INFO: All other parameters are taken from the INI file located in \"~/.rtabmap\"\n"); + if(pm.size()>1) + { + printf(" Overwritten parameters :\n"); + for(ParametersMap::iterator iter = pm.begin(); iter!=pm.end(); ++iter) + { + printf(" %s=%s\n",iter->first.c_str(), iter->second.c_str()); + } + } + printf("\nProcessing images...\n"); + + UTimer iterationTimer; + int imagesProcessed = 0; + std::list > teleopActions; + int maxTeleopActions = 0; // TEST Lip6Indoor with 190, 0->disabled + std::list > actions; + while(loopDataset <= repeat && g_forever) + { + image = camera->takeImage(); + int i=0; + while(image && g_forever) + { + ++imagesProcessed; + iterationTimer.start(); + SMState * smState; + if(i0 + std::vector v(5); + v[0] = 1; + v[1] = 16; + v[2] = 32; + v[3] = 64; + v[4] = 128; + teleopActions.push_back(v); + smState = new SMState(image, teleopActions); + } + else + { + smState = new SMState(image, actions); + } + rtabmap->process(smState); + loopClosureId = rtabmap->getLoopClosureId(); + actions = rtabmap->getActions(); + if(rtabmap->getLoopClosureId()) + { + ++countLoopDetected; + } + image = camera->takeImage(); + if(++count % 100 == 0) + { + printf(" count = %d, loop closures = %d\n", count, countLoopDetected); + std::map wm = rtabmap->getWeights(); + printf(" WM(%d)=[", (int)wm.size()); + for(std::map::iterator iter=wm.begin(); iter!=wm.end();++iter) + { + if(iter != wm.begin()) + { + printf(";"); + } + printf("%d,%d", iter->first, iter->second); + } + printf("]\n"); + } + + // Update generated ground truth matrix + if(groundTruthMat) + { + if(loopClosureId > 0 && loopClosureId-1 < groundTruthMat->cols) + { + cvmSet(groundTruthMat, i, loopClosureId-1, 1); + } + } + + ++i; + + double iterationTime = iterationTimer.ticks(); + + ULogger::flush(); + + if(rate) + { + float delta = rate - iterationTime; + if(delta > 0) + { + uSleep(delta*1000); + } + } + if(rtabmap->getLoopClosureId()) + { + printf(" iteration(%d) actions=%d loop(%d) time=%fs\n", count, (int)actions.size(), rtabmap->getLoopClosureId(), iterationTime); + } + else + { + printf(" iteration(%d) actions=%d time=%fs\n", count, (int)actions.size(), iterationTime); + } + + if(timeThreshold && iterationTime > timeThreshold*100.0f) + { + printf(" ERROR, there is problem, too much time taken... %fs", iterationTime); + break; // there is problem, don't continue + } + } + ++loopDataset; + if(loopDataset <= repeat) + { + camera->init(); + printf(" Beginning loop %d...\n", loopDataset); + } + } + printf("Processing images completed. Loop closures found = %d\n", countLoopDetected); + printf(" Total time = %fs\n", timer.ticks()); + + if(groundTruthMat) + { + if(rtabmap->getTotalMemSize() != groundTruthMat->rows) + { + printf("WARNING : Ground truth matrix size and the image count don't match : Image captured=%d, GroundTruthSize = %d\n", imagesProcessed, groundTruthMat->rows); + } + + // Generate the ground truth file + printf("Generate ground truth to file %s, size of %d\n", (rtabmap->getWorkingDir()+GENERATED_GT_NAME).c_str(), groundTruthMat->rows); + FILE* fout = 0; +#ifdef _MSC_VER + fopen_s(&fout, (rtabmap->getWorkingDir()+GENERATED_GT_NAME).c_str(), "w+"); +#else + fout = fopen((rtabmap->getWorkingDir()+GENERATED_GT_NAME).c_str(), "w+"); +#endif + if(fout) + { + for(int i=0; irows; i++) + { + for(int j=0; jcols; j++) + { + fprintf(fout, "%d", cvmGet(groundTruthMat,i,j)>0?255:0); + if(j+1cols) + { + fprintf(fout," "); + } + } + if(i+1rows) + { + fprintf(fout,"\n"); + } + } + fclose(fout); + fout = 0; + } + else + { + printf("ERROR : Can't generate the ground truth file \"%s\"...\n", (rtabmap->getWorkingDir()+GENERATED_GT_NAME).c_str()); + } + cvReleaseMat(&groundTruthMat); + groundTruthMat = 0; + printf(" Creating ground truth file = %fs\n", timer.ticks()); + } + + if(camera) + { + delete camera; + camera = 0 ; + } + + if(rtabmap) + { + delete rtabmap; + rtabmap = 0; + } + + printf(" Cleanup time = %fs\n", timer.ticks()); + + return 0; +} diff --git a/corelib/DatabaseViewer/CMakeLists.txt b/corelib/DatabaseViewer/CMakeLists.txt new file mode 100644 index 0000000000..2fa8d8db4a --- /dev/null +++ b/corelib/DatabaseViewer/CMakeLists.txt @@ -0,0 +1,57 @@ +### Qt Gui stuff ### +SET(headers_ui + ./MainWindow.h +) + +SET(uis + ./ui/MainWindow.ui +) + +#Generate .h files from the .ui files +QT4_WRAP_UI(moc_uis ${uis}) + +#This will generate moc_* for Qt +QT4_WRAP_CPP(moc_srcs ${headers_ui}) +### Qt Gui stuff end### + + +SET(SRC_FILES + ./main.cpp + ./MainWindow.cpp + ${moc_srcs} + ${moc_uis} +) + +SET(INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/../include + ${CMAKE_CURRENT_SOURCE_DIR}/../src + ${CMAKE_CURRENT_SOURCE_DIR} + ${UTILITE_INCLUDE_DIR} + ${OpenCV_INCLUDE_DIRS} + ${CMAKE_CURRENT_BINARY_DIR} # for qt ui generated in binary dir +) + +INCLUDE(${QT_USE_FILE}) + +SET(LIBRARIES + ${UTILITE_LIBRARY} + ${QT_LIBRARIES} + ${OpenCV_LIBRARIES} + #${QWT5_LIBRARY} +) + +#include files +INCLUDE_DIRECTORIES(${INCLUDE_DIRS}) + +# Add binary called "databaseViewer" that is built from the source file "main.cpp". +# The extension is automatically found. +ADD_EXECUTABLE(databaseViewer WIN32 ${SRC_FILES}) +TARGET_LINK_LIBRARIES(databaseViewer corelib ${LIBRARIES}) + +SET_TARGET_PROPERTIES( databaseViewer + PROPERTIES OUTPUT_NAME ${PROJECT_PREFIX}-databaseViewer) + +INSTALL(TARGETS databaseViewer + RUNTIME DESTINATION bin COMPONENT runtime + LIBRARY DESTINATION lib COMPONENT devel + ARCHIVE DESTINATION lib COMPONENT devel) \ No newline at end of file diff --git a/corelib/DatabaseViewer/MainWindow.cpp b/corelib/DatabaseViewer/MainWindow.cpp new file mode 100644 index 0000000000..ff63f9a435 --- /dev/null +++ b/corelib/DatabaseViewer/MainWindow.cpp @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "MainWindow.h" +#include "ui_MainWindow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "KeypointMemory.h" +#include "rtabmap/core/DBDriver.h" + +MainWindow::MainWindow(QWidget * parent) : + QMainWindow(parent), + memory_(0) +{ + pathDatabase_ = QDir::homePath()+"/Documents/RTAB-Map"; //use home directory by default + + if(!UDirectory::exists(pathDatabase_.toStdString())) + { + pathDatabase_ = QDir::homePath(); + } + + ui_ = new Ui_MainWindow(); + ui_->setupUi(this); + + connect(ui_->actionQuit, SIGNAL(triggered()), this, SLOT(close())); + + // connect actions with custom slots + connect(ui_->actionOpen_database, SIGNAL(triggered()), this, SLOT(openDatabase())); + connect(ui_->actionGenerate_graph_dot, SIGNAL(triggered()), this, SLOT(generateGraph())); + connect(ui_->actionGenerate_local_graph_dot, SIGNAL(triggered()), this, SLOT(generateLocalGraph())); + connect(ui_->actionClean_database, SIGNAL(triggered()), this, SLOT(cleanDatabase())); + connect(ui_->actionClean_local_graph, SIGNAL(triggered()), this, SLOT(cleanLocalGraph())); + + ui_->graphicsView_A->setScene(new QGraphicsScene(this)); + ui_->graphicsView_B->setScene(new QGraphicsScene(this)); + + ui_->horizontalSlider_A->setTracking(false); + ui_->horizontalSlider_B->setTracking(false); + ui_->horizontalSlider_A->setEnabled(false); + ui_->horizontalSlider_B->setEnabled(false); + connect(ui_->horizontalSlider_A, SIGNAL(valueChanged(int)), this, SLOT(sliderAValueChanged(int))); + connect(ui_->horizontalSlider_B, SIGNAL(valueChanged(int)), this, SLOT(sliderBValueChanged(int))); + connect(ui_->horizontalSlider_A, SIGNAL(sliderMoved(int)), this, SLOT(sliderAMoved(int))); + connect(ui_->horizontalSlider_B, SIGNAL(sliderMoved(int)), this, SLOT(sliderBMoved(int))); +} + +MainWindow::~MainWindow() +{ + delete ui_; + if(memory_) + { + delete memory_; + } +} + +void MainWindow::openDatabase() +{ + QString path = QFileDialog::getOpenFileName(this, tr("Select file"), pathDatabase_, tr("Databases (*.db)")); + if(!path.isEmpty()) + { + if(memory_) + { + delete memory_; + memory_ = 0; + imagesMap_.clear(); + ids_.clear(); + } + + std::string driverType = "sqlite3"; + rtabmap::ParametersMap parameters; + parameters.insert(rtabmap::ParametersPair(rtabmap::Parameters::kDbSqlite3InMemory(), "false")); + memory_ = new rtabmap::KeypointMemory(parameters); + if(!memory_) + { + QMessageBox::warning(this, "Database error", tr("Can't create database driver \"%1\"").arg(driverType.c_str())); + } + else if(!memory_->init(driverType, path.toStdString())) + { + QMessageBox::warning(this, "Database error", tr("Can't open database \"%1\"").arg(path)); + } + else + { + pathDatabase_ = path; + updateIds(); + } + } + + +} + +void MainWindow::updateIds() +{ + if(!memory_) + { + return; + } + + std::set ids = memory_->getAllSignatureIds(); + ids_ = QList::fromStdList(std::list(ids.begin(), ids.end())); + ids_.prepend(0); + + UDEBUG("Loaded %d ids", ids_.size()); + + if(ids_.size()) + { + ui_->horizontalSlider_A->setMinimum(0); + ui_->horizontalSlider_B->setMinimum(0); + ui_->horizontalSlider_A->setMaximum(ids_.size()-1); + ui_->horizontalSlider_B->setMaximum(ids_.size()-1); + ui_->horizontalSlider_A->setSliderPosition(0); + ui_->horizontalSlider_B->setSliderPosition(0); + ui_->horizontalSlider_A->setEnabled(true); + ui_->horizontalSlider_B->setEnabled(true); + ui_->label_idA->setText("0"); + ui_->label_idB->setText("0"); + } + else + { + ui_->horizontalSlider_A->setEnabled(false); + ui_->horizontalSlider_B->setEnabled(false); + ui_->label_idA->setText("NaN"); + ui_->label_idB->setText("NaN"); + } +} + +void MainWindow::cleanDatabase() +{ + if(!memory_ || !ids_.size()) + { + QMessageBox::warning(this, tr("Cannot clean the database"), tr("A database (not empty) must must loaded first...\nUse File->Open database.")); + return; + } + + bool ok; + int depth = QInputDialog::getInt(this, tr("Depth around the weighted locations?"), tr("Depth"), 10, 1, 1000, 1, &ok); + if(ok) + { + UINFO("Cleaning..."); + //Remove all signatures with null weight and between two intersections + memory_->cleanLTM(depth); + + //dbDriver_->executeNoResult(std::string("DELETE FROM Signature WHERE loopClosureId!=0;")); + //dbDriver_->executeNoResult(std::string("DELETE FROM Neighbor WHERE NOT EXISTS (SELECT * FROM Signature WHERE Signature.id = Neighbor.sid);")); + //dbDriver_->executeNoResult(std::string("DELETE FROM Neighbor WHERE NOT EXISTS (SELECT * FROM Signature WHERE Signature.id = Neighbor.nid);")); + + // Clean links + //dbDriver_->executeNoResult(std::string("DELETE FROM Map_SS_VW WHERE NOT EXISTS (SELECT * FROM Signature WHERE Signature.id = signatureId);")); + + // Clean words + //dbDriver_->deleteUnreferencedWords(); + + updateIds(); + UINFO("Finished cleaning!"); + } +} + +void MainWindow::cleanLocalGraph() +{ + if(!ids_.size() || !memory_) + { + QMessageBox::warning(this, tr("Cannot generate a graph"), tr("The database is empty...")); + return; + } + bool ok = false; + int id = QInputDialog::getInt(this, tr("Around which location?"), tr("Location ID"), ids_.first(), ids_.first(), ids_.last(), 1, &ok); + + if(ok) + { + int margin = QInputDialog::getInt(this, tr("Depth around the location?"), tr("Margin"), 4, 1, 100, 1, &ok); + if(ok) + { + UTimer timer; + UINFO("Cleaning local graph for location %d", id); + + memory_->cleanLocalGraph(id, margin); + + updateIds(); + UINFO("time=%fs", timer.ticks()); + } + } +} + +void MainWindow::generateGraph() +{ + if(!memory_) + { + QMessageBox::warning(this, tr("Cannot generate a graph"), tr("A database must must loaded first...\nUse File->Open database.")); + return; + } + + QString path = QFileDialog::getSaveFileName(this, tr("Save File"), pathDatabase_+"/Graph.dot", tr("Graphiz file (*.dot)")); + if(!path.isEmpty()) + { + memory_->generateGraph(path.toStdString()); + } +} + +void MainWindow::generateLocalGraph() +{ + if(!ids_.size() || !memory_) + { + QMessageBox::warning(this, tr("Cannot generate a graph"), tr("The database is empty...")); + return; + } + bool ok = false; + int id = QInputDialog::getInt(this, tr("Around which location?"), tr("Location ID"), ids_.first(), ids_.first(), ids_.last(), 1, &ok); + + if(ok) + { + int margin = QInputDialog::getInt(this, tr("Depth around the location?"), tr("Margin"), 4, 1, 100, 1, &ok); + if(ok) + { + QString path = QFileDialog::getSaveFileName(this, tr("Save File"), pathDatabase_+"/Graph" + QString::number(id) + ".dot", tr("Graphiz file (*.dot)")); + if(!path.isEmpty()) + { + std::map ids; + memory_->getNeighborsId(ids, id, margin-1, true); + ids.insert(std::pair(id, 0)); + std::set idsSet; + for(std::map::iterator iter = ids.begin(); iter!=ids.end(); ++iter) + { + idsSet.insert(idsSet.end(), iter->first); + } + memory_->generateGraph(path.toStdString(), idsSet); + } + } + } +} + +void MainWindow::sliderAValueChanged(int value) +{ + ui_->label_indexA->setText(QString::number(value)); + if(value >= 0 && value < ids_.size()) + { + ui_->graphicsView_A->scene()->clear(); + int id = ids_.at(value); + ui_->label_idA->setText(QString::number(id)); + if(id>0) + { + QImage img; + QMap::iterator iter = imagesMap_.find(id); + if(iter == imagesMap_.end()) + { + if(memory_) + { + IplImage * image = memory_->getImage(id); + if(image) + { + img = ipl2QImage(image); + cvReleaseImage(&image); + if(!img.isNull()) + { + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + img.save(&buffer, "JPEG"); // writes image into ba in JPEG format + imagesMap_.insert(id, ba); + } + } + } + } + else + { + img.loadFromData(iter.value(), "JPEG"); + } + + if(!img.isNull()) + { + ui_->graphicsView_A->scene()->addPixmap(QPixmap::fromImage(img)); + } + else + { + ULOGGER_DEBUG("Image is empty"); + } + } + + ui_->label_idA->setText(QString::number(id)); + ui_->graphicsView_A->fitInView(ui_->graphicsView_A->scene()->itemsBoundingRect(), Qt::KeepAspectRatio); + } + else + { + ULOGGER_ERROR("Slider index out of range ?"); + } +} + +void MainWindow::sliderBValueChanged(int value) +{ + ui_->label_indexB->setText(QString::number(value)); + if(value >= 0 && value < ids_.size()) + { + ui_->graphicsView_B->scene()->clear(); + int id = ids_.at(value); + ui_->label_idB->setText(QString::number(id)); + if(id>0) + { + QImage img; + QMap::iterator iter = imagesMap_.find(id); + if(iter == imagesMap_.end()) + { + if(memory_) + { + IplImage * image = memory_->getImage(id); + if(image) + { + img = ipl2QImage(image); + cvReleaseImage(&image); + + if(!img.isNull()) + { + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + img.save(&buffer, "JPEG"); // writes image into ba in JPEG format + imagesMap_.insert(id, ba); + } + } + } + } + else + { + img.loadFromData(iter.value(), "JPEG"); + } + + if(!img.isNull()) + { + ui_->graphicsView_B->scene()->addPixmap(QPixmap::fromImage(img)); + } + else + { + ULOGGER_DEBUG("Image is empty"); + } + } + + ui_->label_idB->setText(QString::number(id)); + ui_->graphicsView_B->fitInView(ui_->graphicsView_B->scene()->itemsBoundingRect(), Qt::KeepAspectRatio); + } + else + { + ULOGGER_ERROR("Slider index out of range ?"); + } +} + +void MainWindow::sliderAMoved(int value) +{ + ui_->label_indexA->setText(QString::number(value)); + if(value>=0 && value < ids_.size()) + { + ui_->label_idA->setText(QString::number(ids_.at(value))); + } + else + { + ULOGGER_ERROR("Slider index out of range ?"); + } +} + +void MainWindow::sliderBMoved(int value) +{ + ui_->label_indexB->setText(QString::number(value)); + if(value>=0 && value < ids_.size()) + { + ui_->label_idB->setText(QString::number(ids_.at(value))); + } + else + { + ULOGGER_ERROR("Slider index out of range ?"); + } +} + +QImage MainWindow::ipl2QImage(const IplImage *newImage) //fct recuperer sur le net, converti un ldImage en QImage +{ + QImage qtemp; + if (newImage && newImage->depth == IPL_DEPTH_8U && cvGetSize(newImage).width>0) + { + int x; + int y; + char* data = newImage->imageData; + + qtemp= QImage(newImage->width, newImage->height,QImage::Format_RGB32 ); + for( y = 0; y < newImage->height; y++, data +=newImage->widthStep ) + { + for( x = 0; x < newImage->width; x++) + { + uint *p = (uint*)qtemp.scanLine (y) + x; + *p = qRgb(data[x * newImage->nChannels+2], data[x * newImage->nChannels+1],data[x * newImage->nChannels]); + } + } + } + else + { + ULOGGER_ERROR("Wrong IplImage format"); + } + return qtemp; +} diff --git a/corelib/DatabaseViewer/MainWindow.h b/corelib/DatabaseViewer/MainWindow.h new file mode 100644 index 0000000000..890fb9fc94 --- /dev/null +++ b/corelib/DatabaseViewer/MainWindow.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef MAINWINDOW_H_ +#define MAINWINDOW_H_ + +#include +#include +#include +#include +#include +#include +#include + +class Ui_MainWindow; + +namespace rtabmap +{ +class Memory; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget * parent = 0); + virtual ~MainWindow(); + +private slots: + void openDatabase(); + void cleanDatabase(); + void cleanLocalGraph(); + void generateGraph(); + void generateLocalGraph(); + void sliderAValueChanged(int); + void sliderBValueChanged(int); + void sliderAMoved(int); + void sliderBMoved(int); + +private: + void updateIds(); + QImage ipl2QImage(const IplImage *newImage); + +private: + Ui_MainWindow * ui_; + QMap imagesMap_; + QList ids_; + rtabmap::Memory * memory_; + QString pathDatabase_; +}; + +#endif /* MAINWINDOW_H_ */ diff --git a/corelib/DatabaseViewer/main.cpp b/corelib/DatabaseViewer/main.cpp new file mode 100644 index 0000000000..e94064a555 --- /dev/null +++ b/corelib/DatabaseViewer/main.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include +#include "MainWindow.h" +#include "utilite/ULogger.h" + +int main(int argc, char * argv[]) +{ + ULogger::setType(ULogger::kTypeConsole); + ULogger::setLevel(ULogger::kInfo); + + QApplication * app = new QApplication(argc, argv); + MainWindow * mainWindow = new MainWindow(); + mainWindow->showNormal(); + + // Now wait for application to finish + app->connect( app, SIGNAL( lastWindowClosed() ), + app, SLOT( quit() ) ); + app->exec();// MUST be called by the Main Thread + + delete mainWindow; + delete app; + + return 0; +} diff --git a/corelib/DatabaseViewer/ui/MainWindow.ui b/corelib/DatabaseViewer/ui/MainWindow.ui new file mode 100644 index 0000000000..550b7c0396 --- /dev/null +++ b/corelib/DatabaseViewer/ui/MainWindow.ui @@ -0,0 +1,202 @@ + + + MainWindow + + + + 0 + 0 + 550 + 318 + + + + Images database viewer + + + + + + + + + + + + + + + + Index : + + + + + + + Id : + + + + + + + + + + + indexA + + + + + + + idA + + + + + + + + + Qt::Horizontal + + + QSlider::TicksAbove + + + + + + + + + + + + + + + + + + + + Index : + + + + + + + Id : + + + + + + + + + + + indexB + + + + + + + idB + + + + + + + + + Qt::Horizontal + + + QSlider::TicksAbove + + + + + + + + + + + + + 0 + 0 + 550 + 22 + + + + + File + + + + + + + + Edit + + + + + + + + + + + + + Open database + + + + + Quit + + + + + Generate graph (.dot) ... + + + + + Generate graph (only weighted locations) ... + + + + + Clean database + + + + + Generate local graph (.dot) ... + + + + + Clean local graph ... + + + + + + diff --git a/corelib/ImagesJoiner/CMakeLists.txt b/corelib/ImagesJoiner/CMakeLists.txt new file mode 100644 index 0000000000..72124e584e --- /dev/null +++ b/corelib/ImagesJoiner/CMakeLists.txt @@ -0,0 +1,32 @@ + +SET(SRC_FILES + main.cpp +) + +SET(INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR} + ${UTILITE_INCLUDE_DIR} + ${OpenCV_INCLUDE_DIRS} +) + +SET(LIBRARIES + ${UTILITE_LIBRARY} + ${OpenCV_LIBS} +) + +# Make sure the compiler can find include files from our library. +INCLUDE_DIRECTORIES(${INCLUDE_DIRS}) + +# Add binary called "imagesJoiner" that is built from the source file "main.cpp". +# The extension is automatically found. +ADD_EXECUTABLE(imagesJoiner ${SRC_FILES}) +TARGET_LINK_LIBRARIES(imagesJoiner ${LIBRARIES}) + +SET_TARGET_PROPERTIES( imagesJoiner + PROPERTIES OUTPUT_NAME ${PROJECT_PREFIX}-imagesJoiner) + +INSTALL(TARGETS imagesJoiner + RUNTIME DESTINATION bin COMPONENT runtime + LIBRARY DESTINATION lib COMPONENT devel + ARCHIVE DESTINATION lib COMPONENT devel) + diff --git a/corelib/ImagesJoiner/main.cpp b/corelib/ImagesJoiner/main.cpp new file mode 100644 index 0000000000..e68ef75ed4 --- /dev/null +++ b/corelib/ImagesJoiner/main.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "utilite/ULogger.h" +#include "utilite/UTimer.h" +#include "utilite/UDirectory.h" +#include "utilite/UConversion.h" +#include +#include + +void showUsage() +{ + printf("Usage:\n" + "imagesJoiner.exe \"# (see below)\" \"type\" [option]\n" + " # : Name pattern is how the file names are formatted (in number).\n" + " Examples: 4 for pictures with 0001.jpg, 0002.jpg, ..., 0010.jpg, 0100.jpg, 1000.jpg, ...\n" + " 1 for pictures with 1.jpg, 2.jpg, ..., 10.jpg, 100.jpg, 1000.jpg, ...\n" + " type : is the extension (jpg, bmp, png...)\n" + " Options:\n" + " -inv option for copying odd images on the right\n" + " -d # destination filename size\n"); + exit(1); +} + +int main(int argc, char * argv[]) +{ + if(argc < 3) + { + showUsage(); + } + + bool inv = false; + unsigned int sizeFileName = std::atoi(argv[1]); + unsigned int sizeTargetFileName = sizeFileName; + + std::string type = argv[2]; + for(int i=3; i sizeB.height ? sizeA.height : sizeB.height; + IplImage* targetImage = cvCreateImage(targetSize, imageA->depth, imageA->nChannels); + if(targetImage) + { + cvSetImageROI( targetImage, cvRect( 0, 0, sizeA.width, sizeA.height ) ); + cvCopy( imageA, targetImage ); + cvSetImageROI( targetImage, cvRect( sizeA.width, 0, sizeB.width, sizeB.height ) ); + cvCopy( imageB, targetImage ); + cvResetImageROI( targetImage ); + + if(!cvSaveImage(fileNameTarget.c_str(), targetImage)) + { + printf("Error : saving to \"%s\" goes wrong...\n", fileNameTarget.c_str()); + } + else + { + printf("Saved \"%s\" \n", fileNameTarget.c_str()); + } + + cvReleaseImage(&targetImage); + } + else + { + printf("Error : can't allocated the target image with size (%d,%d)\n", targetSize.width, targetSize.height); + imagesExist = false; + } + } + else + { + imagesExist = false; + } + + if(imageA) + { + cvReleaseImage(&imageA); + } + if(imageB) + { + cvReleaseImage(&imageB); + } + + counterJoined++; + counterImages += 2; + } + + + + + return 0; +} diff --git a/corelib/WebcamCapture/CMakeLists.txt b/corelib/WebcamCapture/CMakeLists.txt new file mode 100644 index 0000000000..d3c1de2e6e --- /dev/null +++ b/corelib/WebcamCapture/CMakeLists.txt @@ -0,0 +1,33 @@ + +SET(SRC_FILES + main.cpp +) + +SET(INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR} + ${UTILITE_INCLUDE_DIR} + ${OpenCV_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/../include +) + +SET(LIBRARIES + ${UTILITE_LIBRARY} + ${OpenCV_LIBS} +) + +# Make sure the compiler can find include files from our library. +INCLUDE_DIRECTORIES(${INCLUDE_DIRS}) + +# Add binary called "imagesJoiner" that is built from the source file "main.cpp". +# The extension is automatically found. +ADD_EXECUTABLE(webcamCapture ${SRC_FILES}) +TARGET_LINK_LIBRARIES(webcamCapture corelib ${LIBRARIES}) + +SET_TARGET_PROPERTIES( webcamCapture + PROPERTIES OUTPUT_NAME ${PROJECT_PREFIX}-webcamCapture) + +INSTALL(TARGETS webcamCapture + RUNTIME DESTINATION bin COMPONENT runtime + LIBRARY DESTINATION lib COMPONENT devel + ARCHIVE DESTINATION lib COMPONENT devel) + diff --git a/corelib/WebcamCapture/main.cpp b/corelib/WebcamCapture/main.cpp new file mode 100644 index 0000000000..8db42aaeca --- /dev/null +++ b/corelib/WebcamCapture/main.cpp @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "utilite/ULogger.h" +#include "utilite/UTimer.h" +#include "utilite/UDirectory.h" +#include "utilite/UConversion.h" +#include "rtabmap/core/Camera.h" +#include "rtabmap/core/SMState.h" +#include "utilite/UEventsManager.h" +#include "utilite/UEventsHandler.h" +#include "rtabmap/core/CameraEvent.h" +#include +#include +#include + +void showUsage() +{ + printf("Usage:\n" + "webcamCapture [option]\n" + " -device #: Id of the webcam (default 0)\n" + " -width #: Image width (default 640)\n" + " -height #: Image height (default 480)\n" + " -fps #.#: Frame rate (Hz) (default 2.0)\n" + " -show bool: Image shown while capturing (default true)\n" + " -dir \"path\": Path of the images saved (default \"./imagesCaptured\")\n" + " -save bool: Save images captured (default true)\n" + " -startId #: Image id to start with (default 1)\n" + " -ext \"ext\": Image extension (default \"jpg\")\n" + " -debug: Debug trace\n"); + exit(1); +} + +class ImagesHandler : public UEventsHandler +{ +public: + ImagesHandler(const std::string & targetDir, const std::string & ext, int startId, bool show, bool save) : + targetDir_(targetDir), + ext_(ext), + startId_(startId), + show_(show), + save_(save), + id_(startId) + { + if(show_) + { + cvNamedWindow("Webcam", CV_WINDOW_AUTOSIZE); + } + } + virtual ~ImagesHandler() + { + if(show_) + { + cvDestroyWindow("Webcam"); + } + } + +protected: + virtual void handleEvent(UEvent * e) + { + if(e->getClassName().compare("SMStateEvent") == 0) + { + const rtabmap::SMStateEvent * event = (const rtabmap::SMStateEvent*)e; + const rtabmap::SMState * sm = event->getData(); + const IplImage * image = 0; + if(sm) + { + image = sm->getImage(); + } + if(image) + { + if(show_) + { + cvShowImage("Webcam", image); + } + if(save_) + { + std::string fileName = targetDir_ + "/"; + fileName += uNumber2str(id_++); + fileName += "."; + fileName += ext_; + cvSaveImage(fileName.c_str(), image); + printf("Image %s saved!\n", fileName.c_str()); + } + } + else + { + printf("Image is null?!?\n"); + } + } + + } +private: + std::string targetDir_; + std::string ext_; + int startId_; + bool show_; + bool save_; + int id_; +}; + +int main(int argc, char * argv[]) +{ + bool show = true; + int usbDevice = 0; + int imageWidth = 640; + int imageHeight = 480; + float imageRate = 2.0; + int startId = 1; + std::string extension = "jpg"; + bool save = true; + std::string targetDirectory = UDirectory::currentDir(true) + "imagesCaptured"; + + for(int i=1; i 100) + { + showUsage(); + } + ++i; + } + + if(strcmp(argv[i], "-width") == 0 && i+1. + */ + +#pragma once + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include +#include "utilite/UThreadNode.h" +#include "utilite/UEventsHandler.h" +#include "rtabmap/core/Parameters.h" +#include +#include + +class UDirectory; + +namespace rtabmap +{ + +class KeypointDetector; +class KeypointDescriptor; +class SMState; + +/** + * Only encapsulate the image in a newly created SMState. + */ +class RTABMAP_EXP CamPostTreatment +{ +public: + CamPostTreatment(const ParametersMap & parameters = ParametersMap()) { + this->parseParameters(parameters); + } + virtual ~CamPostTreatment() {} + virtual SMState * process(IplImage * image); + virtual void parseParameters(const ParametersMap & parameters) {} +}; + +/** + * Extract keypoints from the image + */ +class RTABMAP_EXP CamKeypointTreatment : public CamPostTreatment +{ +public: + enum DetectorStrategy {kDetectorSurf, kDetectorStar, kDetectorSift, kDetectorUndef}; + enum DescriptorStrategy {kDescriptorSurf, kDescriptorColorSurf, kDescriptorLaplacianSurf, kDescriptorSift, kDescriptorHueSurf, kDescriptorUndef}; + +public: + CamKeypointTreatment(const ParametersMap & parameters = ParametersMap()) : + _keypointDetector(0), + _keypointDescriptor(0) + { + this->parseParameters(parameters); + } + virtual ~CamKeypointTreatment(); + virtual SMState * process(IplImage * image); + virtual void parseParameters(const ParametersMap & parameters); + DetectorStrategy detectorStrategy() const; +private: + KeypointDetector * _keypointDetector; + KeypointDescriptor * _keypointDescriptor; +}; + +/** + * Class Camera + * + */ +class RTABMAP_EXP Camera : + public UThreadNode, + public UEventsHandler +{ +public: + enum State {kStateCapturing, kStateChangingParameters}; + +public: + virtual ~Camera(); + + virtual IplImage * takeImage() = 0; + virtual bool init() = 0; + bool isPaused() const {return !this->isRunning();} + bool isCapturing() const {return this->isRunning();} + unsigned int getImageWidth() const {return _imageWidth;} + unsigned int getImageHeight() const {return _imageHeight;} + void setPostThreatement(CamPostTreatment * strategy); // ownership is transferred + +protected: + /** + * Constructor + * + * @param imageRate : image/second , 0 for fast as the camera can + */ + Camera(float imageRate = 0, bool autoRestart = false, unsigned int imageWidth = 0, unsigned int imageHeight = 0); + + virtual void handleEvent(UEvent* anEvent); + +private: + virtual void mainLoop(); + void process(); + void pushNewState(State newState, const ParametersMap & parameters = ParametersMap()); + virtual void parseParameters(const ParametersMap & parameters) {_postThreatement->parseParameters(parameters);} + +private: + float _imageRate; + int _id; + bool _autoRestart; + unsigned int _imageWidth; + unsigned int _imageHeight; + CamPostTreatment * _postThreatement; + + UMutex _stateMutex; + std::stack _state; + std::stack _stateParam; +}; + + + +///////////////////////// +// CameraImages +///////////////////////// +class RTABMAP_EXP CameraImages : + public Camera +{ +public: + CameraImages(const std::string & path, + int startAt = 1, + bool refreshDir = false, + float imageRate = 0, + bool autoRestart = false, + unsigned int imageWidth = 0, + unsigned int imageHeight = 0); + virtual ~CameraImages(); + + virtual IplImage * takeImage(); + virtual bool init(); + +private: + std::string _path; + int _startAt; + // If the list of files in the directory is refreshed + // on each call of takeImage() + bool _refreshDir; + UDirectory * _dir; + int _count; + std::string _lastFileName; + + + +}; + + + + +///////////////////////// +// CameraVideo +///////////////////////// +class RTABMAP_EXP CameraVideo : + public Camera +{ +public: + enum Source{kVideoFile, kUsbDevice}; + +public: + CameraVideo(int usbDevice = 0, + float imageRate = 0, + bool autoRestart = false, + unsigned int imageWidth = 0, + unsigned int imageHeight = 0); + CameraVideo(const std::string & fileName, + float imageRate = 0, + bool autoRestart = false, + unsigned int imageWidth = 0, + unsigned int imageHeight = 0); + virtual ~CameraVideo(); + + virtual IplImage * takeImage(); + virtual bool init(); + +private: + // File type + std::string _fileName; + + CvCapture* _capture; + Source _src; + + // Usb camera + int _usbDevice; +}; + + + + + + +///////////////////////// +// CameraDatabase +///////////////////////// +class DBDriver; +class RTABMAP_EXP CameraDatabase : + public Camera +{ +public: + CameraDatabase(const std::string & path, + bool ignoreChildren, + float imageRate = 0, + bool autoRestart = false, + unsigned int imageWidth = 0, + unsigned int imageHeight = 0); + virtual ~CameraDatabase(); + + virtual IplImage * takeImage(); + virtual bool init(); + +private: + std::string _path; + bool _ignoreChildren; + std::set::iterator _indexIter; + DBDriver * _dbDriver; + std::set _ids; +}; + + +} // namespace rtabmap diff --git a/corelib/include/rtabmap/core/CameraEvent.h b/corelib/include/rtabmap/core/CameraEvent.h new file mode 100644 index 0000000000..33aea53a30 --- /dev/null +++ b/corelib/include/rtabmap/core/CameraEvent.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef CAMERAEVENT_H_ +#define CAMERAEVENT_H_ + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include "utilite/UEvent.h" +#include + +namespace rtabmap +{ + +class RTABMAP_EXP CameraEvent : + public UEvent +{ +public: + enum Code { + kCodeCtrl, + kCodeNoMoreImages + }; + + enum Cmd { + kCmdUndefined, + kCmdPause, + kCmdChangeParam + }; + +public: + CameraEvent(Cmd command, float imageRate = -1, bool autoRestart = false) : + UEvent(kCodeCtrl), + _command(command), + _imageRate(imageRate), + _autoRestart(autoRestart) + { + } + + CameraEvent() : + UEvent(kCodeNoMoreImages), + _command(kCmdUndefined), + _imageRate(-1) + { + + } + + virtual ~CameraEvent() {} + + virtual std::string getClassName() const {return std::string("CameraEvent");} + + const Cmd & getCommand() const {return _command;} + float getImageRate() const {return _imageRate;} + bool getAutoRestart() const {return _autoRestart;} + +private: + Cmd _command; + float _imageRate; + bool _autoRestart; +}; + +} // namespace rtabmap + +#endif /* CAMERAEVENT_H_ */ diff --git a/corelib/include/rtabmap/core/DBDriver.h b/corelib/include/rtabmap/core/DBDriver.h new file mode 100644 index 0000000000..82bedba48b --- /dev/null +++ b/corelib/include/rtabmap/core/DBDriver.h @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef DBDRIVER_H_ +#define DBDRIVER_H_ + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include +#include +#include +#include +#include +#include "utilite/UMutex.h" +#include "utilite/UThreadNode.h" +#include "rtabmap/core/Parameters.h" + +namespace rtabmap { + +class Signature; +class KeypointSignature; +class VWDictionary; +class VisualWord; + +// Todo This class needs a refactoring, the _dbSafeAccessMutex problem when the trash is emptying (transaction) +// "Of course, it has always been the case and probably always will be +//that you cannot use the same sqlite3 connection in two or more +//threads at the same time. You can use different sqlite3 connections +//at the same time in different threads, or you can move the same +//sqlite3 connection across threads (subject to the constraints above) +//but never, never try to use the same connection simultaneously in +//two or more threads." +// +class RTABMAP_EXP DBDriver : public UThreadNode +{ +public: + virtual ~DBDriver(); + + virtual void parseParameters(const ParametersMap & parameters); + const std::string & getUrl() const {return _url;} + + void beginTransaction() const; + void commit() const; + + void asyncSave(Signature * s); + void asyncSave(VisualWord * s); + void emptyTrashes(bool async = false); + double getEmptyTrashesTime() const {return _emptyTrashesTime;} + +public: + bool addStatisticsAfterRun(int stMemSize, int lastSignAdded, int processMemUsed, int databaseMemUsed) const; + bool addStatisticsAfterRunSurf(int dictionarySize) const; + + bool deleteAllVisualWords() const; + bool deleteAllObsoleteSSVWLinks() const; + bool deleteUnreferencedWords() const; + + bool addNeighbor(int id, int neighbor, const std::list > & actuatorStates); + bool removeNeighbor(int id, int neighbor); + +public: + // Mutex-protected methods of abstract versions below + bool getSignature(int signatureId, Signature ** s); + bool getVisualWord(int wordId, VisualWord ** vw); + + bool openConnection(const std::string & url); + void closeConnection(); + bool isConnected() const; + long getMemoryUsed() const; // In bytes + + bool executeNoResult(const std::string & sql) const; + + // Update + bool changeWordsRef(const std::map & refsToChange); // + bool deleteWords(const std::vector & ids); + + // Load objects + bool load(VWDictionary * dictionary) const; + bool loadLastSignatures(std::list & signatures) const; + bool loadKeypointSignatures(const std::list & ids, std::list & signatures, bool onlyParents = false); + bool loadWords(const std::list & wordIds, std::list & vws); + + // Specific queries... + bool getImage(int id, IplImage ** img) const; + bool getNeighborIds(int signatureId, std::set & neighbors) const; + bool loadNeighbors(int signatureId, std::map > > & neighbors) const; + bool getWeight(int signatureId, int & weight) const; + bool getLoopClosureId(int signatureId, int & loopId) const; + bool getImageCompressed(int id, CvMat ** compressed) const; + bool getAllSignatureIds(std::set & ids) const; + bool getLastSignatureId(int & id) const; + bool getLastVisualWordId(int & id) const; + bool getSurfNi(int signatureId, int & ni) const; + bool getChildrenIds(int signatureId, std::list & ids) const; + bool getHighestWeightedSignatures(unsigned int count, std::multimap & ids) const; + +protected: + DBDriver(const ParametersMap & parameters = ParametersMap()); + +private: + virtual bool connectDatabaseQuery(const std::string & url) = 0; + virtual void disconnectDatabaseQuery() = 0; + virtual bool isConnectedQuery() const = 0; + virtual long getMemoryUsedQuery() const = 0; // In bytes + + virtual bool executeNoResultQuery(const std::string & sql) const = 0; + + virtual bool changeWordsRefQuery(const std::map & refsToChange) const = 0; // + virtual bool deleteWordsQuery(const std::vector & ids) const = 0; + virtual bool getNeighborIdsQuery(int signatureId, std::set & neighbors) const = 0; + virtual bool getWeightQuery(int signatureId, int & weight) const = 0; + virtual bool getLoopClosureIdQuery(int signatureId, int & loopId) const = 0; + virtual bool addNeighborQuery(int id, int neighbor, const std::list > & actuatorStates) const = 0; + + virtual bool saveQuery(const std::vector & visualWords) const = 0; + virtual bool updateQuery(const std::list & signatures) const = 0; + virtual bool saveQuery(const KeypointSignature * ss) const = 0; + virtual bool saveQuery(const std::list & signatures) const = 0; + + // Load objects + virtual bool loadQuery(VWDictionary * dictionary) const = 0; + virtual bool loadLastSignaturesQuery(std::list & signatures) const = 0; + virtual bool loadQuery(int signatureId, Signature ** s) const = 0; + virtual bool loadQuery(int wordId, VisualWord ** vw) const = 0; + virtual bool loadQuery(int signatureId, KeypointSignature * ss) const = 0; + virtual bool loadKeypointSignaturesQuery(const std::list & ids, std::list & signatures, bool onlyParents = false) const = 0; + virtual bool loadWordsQuery(const std::list & wordIds, std::list & vws) const = 0; + virtual bool loadNeighborsQuery(int signatureId, std::map > > & neighbors) const = 0; + + virtual bool getImageCompressedQuery(int id, CvMat ** compressed) const = 0; + virtual bool getAllSignatureIdsQuery(std::set & ids) const = 0; + virtual bool getLastSignatureIdQuery(int & id) const = 0; + virtual bool getLastVisualWordIdQuery(int & id) const = 0; + virtual bool getSurfNiQuery(int signatureId, int & ni) const = 0; + virtual bool getChildrenIdsQuery(int signatureId, std::list & ids) const = 0; + virtual bool getHighestWeightedSignaturesQuery(unsigned int count, std::multimap & signatures) const = 0; + +private: + //non-abstract methods + bool saveOrUpdate(const std::vector & signatures) const; + + //thread stuff + virtual void mainLoop(); + virtual void killCleanup(); + +private: + UMutex _transactionMutex; + std::map _trashSignatures;// + std::map _trashVisualWords; // + UMutex _trashesMutex; + UMutex _dbSafeAccessMutex; + USemaphore _addSem; + unsigned int _minSignaturesToSave; + unsigned int _minWordsToSave; + bool _asyncWaiting; + double _emptyTrashesTime; + std::string _url; +}; + +} + +#endif /* DBDRIVER_H_ */ diff --git a/corelib/include/rtabmap/core/DBDriverFactory.h b/corelib/include/rtabmap/core/DBDriverFactory.h new file mode 100644 index 0000000000..635f8f7ee6 --- /dev/null +++ b/corelib/include/rtabmap/core/DBDriverFactory.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef DBDRIVERFACTORY_H_ +#define DBDRIVERFACTORY_H_ + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines +#include "rtabmap/core/Parameters.h" +#include + +namespace rtabmap { + +class DBDriver; + +class RTABMAP_EXP DBDriverFactory +{ +public: + static DBDriver * createDBDriver(const std::string & dbDriverName, const ParametersMap & parameters = ParametersMap()); +public: + DBDriverFactory(); + virtual ~DBDriverFactory(); +}; + +} + +#endif /* DBDRIVERFACTORY_H_ */ diff --git a/corelib/include/rtabmap/core/EpipolarGeometry.h b/corelib/include/rtabmap/core/EpipolarGeometry.h new file mode 100644 index 0000000000..e8b20b9e4c --- /dev/null +++ b/corelib/include/rtabmap/core/EpipolarGeometry.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#pragma once + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include + +namespace rtabmap +{ + +//epipolar geometry +void RTABMAP_EXP findEpipolesFromF(const cv::Mat & fundamentalMatrix, cv::Vec3d & e1, cv::Vec3d & e2); +void RTABMAP_EXP findPFromF(const cv::Mat & fundamentalMatrix, cv::Mat & p2, cv::Vec3d e2 = cv::Vec3d()); + +} // namespace rtabmap diff --git a/corelib/include/rtabmap/core/KeypointDescriptor.h b/corelib/include/rtabmap/core/KeypointDescriptor.h new file mode 100644 index 0000000000..e24809757d --- /dev/null +++ b/corelib/include/rtabmap/core/KeypointDescriptor.h @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef KEYPOINTDESCRIPTOR_H_ +#define KEYPOINTDESCRIPTOR_H_ + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include +#include +#include +#include +#include "rtabmap/core/Parameters.h" + +namespace rtabmap { + +class RTABMAP_EXP KeypointDescriptor { +public: + virtual ~KeypointDescriptor(); + + std::list > generateDescriptors(const IplImage * image, const std::list & keypoints) const; + void setChildDescriptor(KeypointDescriptor * childDescriptor); + virtual void parseParameters(const ParametersMap & parameters); + +protected: + KeypointDescriptor(const ParametersMap & parameters = ParametersMap(), KeypointDescriptor * childDescriptor = 0); + const KeypointDescriptor * getChildDescriptor() const {return _childDescriptor;} + +private: + virtual std::list > _generateDescriptors( + const IplImage * image, + const std::list & keypoints) const = 0; + +private: + KeypointDescriptor * _childDescriptor; +}; + +//SURFDescriptor +class RTABMAP_EXP SURFDescriptor : public KeypointDescriptor +{ +public: + SURFDescriptor(const ParametersMap & parameters = ParametersMap(), KeypointDescriptor * childDescriptor = 0); + virtual ~SURFDescriptor(); + virtual void parseParameters(const ParametersMap & parameters); +private: + virtual std::list > _generateDescriptors(const IplImage * image, const std::list & keypoints) const; + +private: + cv::SURF _surf; + bool _gpuVersion; + bool _upright; +}; + +//SIFTDescriptor +class RTABMAP_EXP SIFTDescriptor : public KeypointDescriptor +{ +public: + SIFTDescriptor(const ParametersMap & parameters = ParametersMap(), KeypointDescriptor * childDescriptor = 0); + virtual ~SIFTDescriptor(); + virtual void parseParameters(const ParametersMap & parameters); +private: + virtual std::list > _generateDescriptors(const IplImage * image, const std::list & keypoints) const; + +private: + cv::SIFT::CommonParams _commonParams; + cv::SIFT::DescriptorParams _descriptorParams; +}; + +//LaplacianDescriptor +class RTABMAP_EXP LaplacianDescriptor : public KeypointDescriptor +{ +public: + LaplacianDescriptor(const ParametersMap & parameters = ParametersMap(), KeypointDescriptor * childDescriptor = 0); + virtual ~LaplacianDescriptor(); + virtual void parseParameters(const ParametersMap & parameters); +private: + virtual std::list > _generateDescriptors(const IplImage * image, const std::list & keypoints) const; +}; + +//MinMax ColorDescriptor +class RTABMAP_EXP ColorDescriptor : public KeypointDescriptor +{ +public: + ColorDescriptor(const ParametersMap & parameters = ParametersMap(), KeypointDescriptor * childDescriptor = 0); + virtual ~ColorDescriptor(); + virtual void parseParameters(const ParametersMap & parameters); +protected: + void getCircularROI(int R, std::vector & RxV) const; +private: + virtual std::list > _generateDescriptors(const IplImage * image, const std::list & keypoints) const; +}; + +//MinMax HueDescriptor +class RTABMAP_EXP HueDescriptor : public ColorDescriptor +{ +public: + HueDescriptor(const ParametersMap & parameters = ParametersMap(), KeypointDescriptor * childDescriptor = 0); + virtual ~HueDescriptor(); + virtual void parseParameters(const ParametersMap & parameters); +private: + virtual std::list > _generateDescriptors(const IplImage * image, const std::list & keypoints) const; + + // assuming that rgb values are normalized [0,1] + float rgb2hue(float r, float g, float b) const; + + // assuming that rgb values are normalized [0,1] + inline float rgb2saturation(float r, float g, float b) const + { + float min = r; + min. + */ + +#ifndef KEYPOINTDETECTOR_H_ +#define KEYPOINTDETECTOR_H_ + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include +#include +#include +#include +#include "rtabmap/core/Parameters.h" + +namespace rtabmap +{ + +class VWDictionary; + +class RTABMAP_EXP KeypointDetector +{ +public: + virtual ~KeypointDetector() {} + std::list generateKeypoints(const IplImage * image); + virtual void parseParameters(const ParametersMap & parameters); + unsigned int getWordsPerImageTarget() const {return _wordsPerImageTarget;} + double getAdaptiveResponseThr() const {return _adaptiveResponseThr;} + virtual double getMinimumResponseThr() const = 0; + bool isUsingAdaptiveResponseThr() const {return _usingAdaptiveResponseThr;} + void setRoi(const std::string & roi); +protected: + KeypointDetector(const ParametersMap & parameters = ParametersMap()); + void setAdaptiveResponseThr(float adaptiveResponseThr) {_adaptiveResponseThr = adaptiveResponseThr;} +private: + virtual std::list _generateKeypoints(const IplImage * image, const cv::Rect & roi) const = 0; + cv::Rect computeRoi(const IplImage * image) const; +private: + unsigned int _wordsPerImageTarget; + bool _usingAdaptiveResponseThr; + double _adaptiveResponseThr; + std::vector _roiRatios; // size 4 +}; + +//SURFDetector +class RTABMAP_EXP SURFDetector : public KeypointDetector +{ +public: + SURFDetector(const ParametersMap & parameters = ParametersMap()); + virtual ~SURFDetector(); + virtual void parseParameters(const ParametersMap & parameters); + virtual double getMinimumResponseThr() const {return _surf.hessianThreshold;}; +private: + virtual std::list _generateKeypoints(const IplImage * image, const cv::Rect & roi) const; +private: + cv::SURF _surf; + bool _gpuVersion; + bool _upright; +}; + +//SIFTDetector +class RTABMAP_EXP SIFTDetector : public KeypointDetector +{ +public: + SIFTDetector(const ParametersMap & parameters = ParametersMap()); + virtual ~SIFTDetector(); + virtual void parseParameters(const ParametersMap & parameters); + virtual double getMinimumResponseThr() const {return _detectorParams.threshold;}; +private: + virtual std::list _generateKeypoints(const IplImage * image, const cv::Rect & roi) const; +private: + cv::SIFT::CommonParams _commonParams; + cv::SIFT::DetectorParams _detectorParams; +}; + +//StarDetector +class RTABMAP_EXP StarDetector : public KeypointDetector +{ +public: + StarDetector(const ParametersMap & parameters = ParametersMap()); + virtual ~StarDetector(); + virtual void parseParameters(const ParametersMap & parameters); + virtual double getMinimumResponseThr() const {return (double)_star.responseThreshold;}; +private: + virtual std::list _generateKeypoints(const IplImage * image, const cv::Rect & roi) const; +private: + cv::StarDetector _star; +}; + +} + +#endif /* KEYPOINTDETECTOR_H_ */ diff --git a/corelib/include/rtabmap/core/Parameters.h b/corelib/include/rtabmap/core/Parameters.h new file mode 100644 index 0000000000..aabff46cfb --- /dev/null +++ b/corelib/include/rtabmap/core/Parameters.h @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef PARAMETERS_H_ +#define PARAMETERS_H_ + +// default parameters +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines +#include "utilite/UEvent.h" +#include +#include +#include "utilite/UDestroyer.h" + +namespace rtabmap +{ + +typedef std::map ParametersMap; // Key, value +typedef std::pair ParametersPair; + +/** + * Macro used to create parameter's key and default value. + * This macro must be used only in the Parameters class definition (in this file). + * They are automatically added to the default parameters map of the class Parameters. + * Example: + * @code + * //for PARAM(Video, ImageWidth, int, 640), the output will be : + * public: + * static std::string kVideoImageWidth() {return std::string("Video/ImageWidth");} + * static int defaultVideoImageWidth() {return 640;} + * private: + * class DummyVideoImageWidth { + * public: + * DummyVideoImageWidth() {parameters_.insert(ParametersPair("Video/ImageWidth", "640"));} + * }; + * DummyVideoImageWidth dummyVideoImageWidth; + * @endcode + */ +#define RTABMAP_PARAM(PREFIX, NAME, TYPE, DEFAULT_VALUE) \ + public: \ + static std::string k##PREFIX##NAME() {return std::string(#PREFIX "/" #NAME);} \ + static TYPE default##PREFIX##NAME() {return DEFAULT_VALUE;} \ + private: \ + class Dummy##PREFIX##NAME { \ + public: \ + Dummy##PREFIX##NAME() {parameters_.insert(ParametersPair(#PREFIX "/" #NAME, #DEFAULT_VALUE));} \ + }; \ + Dummy##PREFIX##NAME dummy##PREFIX##NAME; +// end define PARAM + +/** + * It's the same as the macro PARAM but it should be used for string parameters. + * Macro used to create parameter's key and default value. + * This macro must be used only in the Parameters class definition (in this file). + * They are automatically added to the default parameters map of the class Parameters. + * Example: + * @code + * //for PARAM_STR(Video, TextFileName, "Hello_world"), the output will be : + * public: + * static std::string kVideoFileName() {return std::string("Video/FileName");} + * static std::string defaultVideoFileName() {return "Hello_world";} + * private: + * class DummyVideoFileName { + * public: + * DummyVideoFileName() {parameters_.insert(ParametersPair("Video/FileName", "Hello_world"));} + * }; + * DummyVideoFileName dummyVideoFileName; + * @endcode + */ +#define RTABMAP_PARAM_STR(PREFIX, NAME, DEFAULT_VALUE) \ + public: \ + static std::string k##PREFIX##NAME() {return std::string(#PREFIX "/" #NAME);} \ + static std::string default##PREFIX##NAME() {return DEFAULT_VALUE;} \ + private: \ + class Dummy##PREFIX##NAME { \ + public: \ + Dummy##PREFIX##NAME() {parameters_.insert(ParametersPair(#PREFIX "/" #NAME, DEFAULT_VALUE));} \ + }; \ + Dummy##PREFIX##NAME dummy##PREFIX##NAME; +// end define PARAM + +/** + * Class Parameters. + * This class is used to manage all custom parameters + * we want in the application. It was designed to be very easy to add + * a new parameter (just by adding one line of code). + * The macro PARAM(PREFIX, NAME, TYPE, DEFAULT_VALUE) is + * used to create a parameter in this class. A parameter can be accessed after by + * Parameters::defaultPARAMETERNAME() for the default value, Parameters::kPARAMETERNAME for his key (parameter name). + * The class provides also a general map containing all the parameter's key and + * default value. This map can be accessed anywhere in the application by + * Parameters::getDefaultParameters(); + * Example: + * @code + * //Defining a parameter in this class with the macro PARAM: + * PARAM(Video, ImageWidth, int, 640); + * + * // Now from anywhere in the application (Parameters is a singleton) + * int width = Parameters::defaultVideoImageWidth(); // theDefaultValue = 640 + * std::string theKey = Parameters::kVideoImageWidth(); // theKey = "Video/ImageWidth" + * std::string strValue = Util::value(Parameters::getDefaultParameters(), theKey); // strValue = "640" + * @endcode + * @see getDefaultParameters() + * TODO Add a detailed example with simple classes + */ +class RTABMAP_EXP Parameters +{ + // Rtabmap parameters + RTABMAP_PARAM(Rtabmap, VhStrategy, int, 0); // Simple 0, Epipolar 1 + RTABMAP_PARAM(Rtabmap, PublishStats, bool, true); // Publishing statistics + RTABMAP_PARAM(Rtabmap, ReactivationThr, float, 0.0); // Reactivation threshold + RTABMAP_PARAM(Rtabmap, TimeThr, float, 0.7); // Maximum time allowed for the detector (s) (0 means infinity) + RTABMAP_PARAM(Rtabmap, DisableReactivation, bool, false); // Memory reactivation when a loop closure occurs : 0=enable, 1=disable + RTABMAP_PARAM(Rtabmap, SMStateBufferSize, int, 1); // Data buffer size (0 min inf) + RTABMAP_PARAM(Rtabmap, MinMemorySizeForLoopDetection, unsigned int, 15); //Minimum size of the memory to create loop closure hypotheses + RTABMAP_PARAM_STR(Rtabmap, WorkingDirectory, Parameters::getDefaultWorkingDirectory()); // Working directory + RTABMAP_PARAM(Rtabmap, LocalGraphCleaned, bool, false); // Clean the neighborhood of the retrieved id + RTABMAP_PARAM(Rtabmap, MaxRetrieved, unsigned int, 2); // Maximum locations retrieved at the same time from LTM + + // Hypotheses selection + RTABMAP_PARAM(Rtabmap, LoopThr, float, 0.10); // Loop closing threshold + RTABMAP_PARAM(Rtabmap, LoopRatio, float, 0.90); // The loop closure hypothesis must be over LoopRatio x lastHypothesisValue + + // Memory + RTABMAP_PARAM(Mem, SimilarityThr, float, 0.20); // Similarity between the last signature and neighbor + RTABMAP_PARAM(Mem, SimilarityOnlyLast, bool, false); // Only compare to the last signature in STM, otherwise it compares to all signatures in STM + RTABMAP_PARAM(Mem, RawDataKept, bool, false); // Keep raw data + RTABMAP_PARAM(Mem, MaxStMemSize, unsigned int, 25); // Short-time memory size + RTABMAP_PARAM(Mem, CommonSignatureUsed, bool, true); // A common signature/virtual place is automatically updated with id -1 + RTABMAP_PARAM(Mem, IncrementalMemory, bool, true); + RTABMAP_PARAM(Mem, DatabaseCleaned, bool, true); // Delete old signatures in the database (the ones which can't never be reactivated) + RTABMAP_PARAM(Mem, DelayRequired, int, 10); // Delay (in iterations) required to transfer signatures + RTABMAP_PARAM(Mem, RecentWmRatio, float, 0.2); // Ratio of locations after the last loop closure in WM that cannot be transferred + + // KeypointMemory (Keypoint-based) + RTABMAP_PARAM(Kp, NNStrategy, int, 2); // Naive 0, kdTree 1, kdForest 2 + RTABMAP_PARAM(Kp, IncrementalDictionary, bool, true); + RTABMAP_PARAM(Kp, WordsPerImage, int, 400); + RTABMAP_PARAM(Kp, BadSignRatio, float, 0.05); //Bad signature ratio (less than Ratio x AverageWordsPerImage = bad) + RTABMAP_PARAM(Kp, MinDistUsed, bool, false); // The nearest neighbor must have a distance < minDist + RTABMAP_PARAM(Kp, MinDist, float, 0.05); // Matching a descriptor with a word (euclidean distance ^ 2) + RTABMAP_PARAM(Kp, NndrUsed, bool, true); // If NNDR ratio is used + RTABMAP_PARAM(Kp, NndrRatio, float, 0.8); // NNDR ratio (A matching pair is detected, if its distance is closer than X times the distance of the second nearest neighbor.) + RTABMAP_PARAM(Kp, MaxLeafs, int, 64); // Maximum number of leafs checked (when using kd-trees) + RTABMAP_PARAM(Kp, DetectorStrategy, int, 0); // Surf detector 0, Star detector 1 + RTABMAP_PARAM(Kp, DescriptorStrategy, int, 0); // kDescriptorSurf=0, kDescriptorColorSurf, kDescriptorLaplacianSurf, kDescriptorSift, kDescriptorHueSurf, kDescriptorUndef + RTABMAP_PARAM(Kp, UsingAdaptiveResponseThr, bool, false); + RTABMAP_PARAM(Kp, ReactivatedWordsComparedToNewWords, bool, true); //Reactivated words are compared to the last words added in the dictionary (which are not indexed) + RTABMAP_PARAM(Kp, TfIdfLikelihoodUsed, bool, false); // Use of the td-idf strategy to compute the likelihood + RTABMAP_PARAM(Kp, Parallelized, bool, true); // If the dictionary update and signature creation were parallelized + RTABMAP_PARAM(Kp, TfIdfNormalized, bool, false); // If tf-idf weighting is normalized by the words count ratio between compared signatures + RTABMAP_PARAM_STR(Kp, RoiRatios, "0.0 0.0 0.0 0.0"); // Region of interest ratios [left, right, top, bottom] + RTABMAP_PARAM_STR(Kp, DictionaryPath, ""); // Path of the pre-computed dictionary + + //Database + RTABMAP_PARAM(Db, MinSignaturesToSave, int, 20); // Minimum signatures needed in the trash to save them (empty trash thread) + RTABMAP_PARAM(Db, MinWordsToSave, int, 4000); // Minimum visual words needed in the trash to save them (empty trash thread) + RTABMAP_PARAM(DbSqlite3, InMemory, bool, false); // Using database in the memory instead of a file on the hard disk + RTABMAP_PARAM(DbSqlite3, CacheSize, unsigned int, 2000); // Sqlite cache size (default is 2000) + RTABMAP_PARAM(DbSqlite3, JournalMode, int, 0); // 0=DELETE, 1=TRUNCATE, 2=PERSIST, 3=MEMORY, 4=OFF (see sqlite3 doc : "PRAGMA journal_mode") + + RTABMAP_PARAM(SURF, Extended, bool, false); // true=128, false=64 + RTABMAP_PARAM(SURF, HessianThreshold, float, 100.0); + RTABMAP_PARAM(SURF, Octaves, int, 4); + RTABMAP_PARAM(SURF, OctaveLayers, int, 2); + RTABMAP_PARAM(SURF, GpuVersion, bool, false); + RTABMAP_PARAM(SURF, Upright, bool, false); // U-SURF + + RTABMAP_PARAM(SIFT, Threshold, double, 0.006667); // true=128, false=64 + RTABMAP_PARAM(SIFT, EdgeThreshold, double, 10.0); + + RTABMAP_PARAM(Star, MaxSize, int, 45); + RTABMAP_PARAM(Star, ResponseThreshold, int, 30); + RTABMAP_PARAM(Star, LineThresholdProjected, int, 10); + RTABMAP_PARAM(Star, LineThresholdBinarized, int, 8); + RTABMAP_PARAM(Star, SuppressNonmaxSize, int, 5); + + // BayesFilter + RTABMAP_PARAM(Bayes, VirtualPlacePriorThr, float, 0.9); // Virtual place prior + RTABMAP_PARAM_STR(Bayes, PredictionLC, "0.1 0.24 0.18 0.1 0.04 0.01"); // Prediction of loop closures (Gaussian-like, must be pair size) - Format: {VirtualPlaceProb, LoopClosureProb, BackwardNeighborLvl1, ForwardNeighborLvl1, BackwardNeighborLvl2, ForwardNeighborLvl2, ...} + + // Verify hypotheses + RTABMAP_PARAM(VhEp, MatchCountMin, int, 8); // Minimum of matching visual words pairs to accept the loop hypothesis + RTABMAP_PARAM(VhEp, RansacParam1, float, 3.0); // Fundamental matrix (see cvFindFundamentalMat()): Max distance (in pixels) from the epipolar line for a point to be inlier + RTABMAP_PARAM(VhEp, RansacParam2, float, 0.99); // Fundamental matrix (see cvFindFundamentalMat()): Performance of the RANSAC + +public: + virtual ~Parameters(); + static const ParametersMap & getDefaultParameters(); + +private: + Parameters(); + static Parameters * getInstance(); + const ParametersMap & getParameters() const; + void addParameter(const std::string & key, const std::string & value); + static std::string getDefaultWorkingDirectory(); + +private: + static Parameters * instance_; + static UDestroyer destroyer_; + static ParametersMap parameters_; +}; + +/** + * The parameters event. This event is used to send + * parameters across the threads. + */ +class ParamEvent : public UEvent +{ +public: + ParamEvent(const ParametersMap & parameters) : UEvent(0), parameters_(parameters) {} + ParamEvent(const std::string & parameterKey, const std::string & parameterValue) : UEvent(0) + { + parameters_.insert(std::pair(parameterKey, parameterValue)); + } + ~ParamEvent() {} + virtual std::string getClassName() const {return "ParamEvent";} + + const ParametersMap & getParameters() const {return parameters_;} + +private: + ParametersMap parameters_; /**< The parameters map (key,value). */ +}; + +} + +#endif /* PARAMETERS_H_ */ + diff --git a/corelib/include/rtabmap/core/Rtabmap.h b/corelib/include/rtabmap/core/Rtabmap.h new file mode 100644 index 0000000000..3a8a563e72 --- /dev/null +++ b/corelib/include/rtabmap/core/Rtabmap.h @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef CTABMAP_H_ +#define CTABMAP_H_ + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include "utilite/UThreadNode.h" +#include "utilite/UEventsHandler.h" +#include "utilite/USemaphore.h" +#include "utilite/UMutex.h" +#include "utilite/UVariant.h" +#include "rtabmap/core/RtabmapEvent.h" +#include "rtabmap/core/Parameters.h" +#include +#include +#include +#include + +namespace rtabmap +{ + +class Signature; + +class VerifyHypotheses; +class Memory; +class BayesFilter; +class SMState; + +class RTABMAP_EXP Rtabmap : + public UThreadNode, + public UEventsHandler +{ +public: + enum State { + kStateIdle, + kStateDetecting, + kStateReseting, + kStateChangingParameters, + kStateDumpingMemory, + kStateDumpingPrediction, + kStateGeneratingGraph, + kStateDeletingMemory + }; + + enum VhStrategy {kVhSimple, kVhEpipolar, kVhUndef}; + + static const char * kDefaultIniFileName; + static const char * kDefaultIniFilePath; + +public: + static std::string getVersion(); + static std::string getIniFilePath(); + static void readParameters(const char * configFile, ParametersMap & parameters); + static void writeParameters(const char * configFile, const ParametersMap & parameters); + +public: + Rtabmap(); + virtual ~Rtabmap(); + + void process(SMState * data); + void dumpData(); + + void init(const ParametersMap & param); + void init(const char * configFile = 0); + + const std::string & getWorkingDir() const {return _wDir;} + int getLoopClosureId() const; + int getLastSignatureId() const; + const std::list > & getActions() const {return _actions;} + std::list getWorkingMem() const; + std::set getStMem() const; + std::map getWeights() const; + int getTotalMemSize() const; + const std::string & getGraphFileName() const {return _graphFileName;} + + void setReactivationDisabled(bool reactivationDisabled); + void setMaxTimeAllowed(float maxTimeAllowed); // in sec + void setDataBufferSize(int size); + void setWorkingDirectory(std::string path); + void setGraphFileName(const std::string & fileName) {_graphFileName = fileName;} + + void adjustLikelihood(std::map & likelihood) const; + void selectHypotheses(const std::map & posterior, + std::list > & hypotheses, + bool useNeighborSum) const; + +protected: + virtual void handleEvent(UEvent * anEvent); + +private: + virtual void mainLoop(); + virtual void killCleanup(); + virtual void startInit(); + void process(); + void addSMState(SMState * data); + SMState * getSMState(); + void setupLogFiles(); + void releaseAllStrategies(); + void pushNewState(State newState, const ParametersMap & parameters = ParametersMap()); + void dumpPrediction() const; + void parseParameters(const ParametersMap & parameters); + +private: + // Modifiable parameters + bool _publishStats; + bool _reactivationDisabled; + float _maxTimeAllowed; // in sec + int _smStateBufferMaxSize; + unsigned int _minMemorySizeForLoopDetection; + float _loopThr; + float _loopRatio; + float _remThr; + bool _localGraphCleaned; + unsigned int _maxRetrieved; + + int _lcHypothesisId; + int _reactivateId; + float _highestHypothesisValue; + unsigned int _spreadMargin; + int _lastLoopClosureId; + std::list > _actions; + + UMutex _stateMutex; + std::stack _state; + std::stack _stateParam; + + std::list _smStateBuffer; + UMutex _smStateBufferMutex; + USemaphore _newSMStateSem; + + // Abstract classes containing all loop closure + // strategies for a type of signature or configuration. + VerifyHypotheses * _vhStrategy; + BayesFilter * _bayesFilter; + + Memory * _memory; + + FILE* _foutFloat; + FILE* _foutInt; + + std::string _wDir; + std::string _graphFileName; +}; + +#endif /* CTABMAP_H_ */ + +} // namespace rtabmap diff --git a/corelib/include/rtabmap/core/RtabmapEvent.h b/corelib/include/rtabmap/core/RtabmapEvent.h new file mode 100644 index 0000000000..57410006c9 --- /dev/null +++ b/corelib/include/rtabmap/core/RtabmapEvent.h @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef RTABMAPEVENT_H_ +#define RTABMAPEVENT_H_ + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include "utilite/UEvent.h" +#include +#include +#include +#include "utilite/ULogger.h" +#include +#include + +namespace rtabmap +{ + +#define RTABMAP_STATS(PREFIX, NAME, UNIT) \ + public: \ + static std::string k##PREFIX##NAME() {return #PREFIX "/" #NAME "/" #UNIT;} \ + private: \ + class Dummy##PREFIX##NAME { \ + public: \ + Dummy##PREFIX##NAME() {if(!_defaultDataInitialized)_defaultData.insert(std::pair(#PREFIX "/" #NAME "/" #UNIT, 0.0f));} \ + }; \ + Dummy##PREFIX##NAME dummy##PREFIX##NAME; + +class RTABMAP_EXP Statistics +{ + RTABMAP_STATS(Loop, Closure_id,); + RTABMAP_STATS(Loop, Rejected_reason,); + RTABMAP_STATS(Loop, Highest_hypothesis_id,); + RTABMAP_STATS(Loop, Highest_hypothesis_value,); + RTABMAP_STATS(Loop, Vp_likelihood,); + RTABMAP_STATS(Loop, ReactivateId,); + RTABMAP_STATS(Loop, Hypothesis_ratio,); + + RTABMAP_STATS(Memory, Working_memory_size,); + RTABMAP_STATS(Memory, Short_time_memory_size,); + RTABMAP_STATS(Memory, Database_size, MB); + RTABMAP_STATS(Memory, Process_memory_used, MB); + RTABMAP_STATS(Memory, Signatures_removed,); + RTABMAP_STATS(Memory, Signatures_reactivated,); + RTABMAP_STATS(Memory, Images_buffered,); + + RTABMAP_STATS(Timing, Memory_update, ms); + RTABMAP_STATS(Timing, Cleaning_neighbors, ms); + RTABMAP_STATS(Timing, Reactivation, ms); + RTABMAP_STATS(Timing, Likelihood_computation, ms); + RTABMAP_STATS(Timing, Posterior_computation, ms); + RTABMAP_STATS(Timing, Hypotheses_creation, ms); + RTABMAP_STATS(Timing, Hypotheses_validation, ms); + RTABMAP_STATS(Timing, Statistics_creation, ms); + RTABMAP_STATS(Timing, Memory_cleanup, ms); + RTABMAP_STATS(Timing, Total, ms); + RTABMAP_STATS(Timing, Forgetting, ms); + RTABMAP_STATS(Timing, Emptying_memory_trash, ms); + + RTABMAP_STATS(, Parent_id,); + RTABMAP_STATS(, Hypothesis_reactivated,); + + RTABMAP_STATS(Keypoint, Dictionary_size, words); + RTABMAP_STATS(Keypoint, Response_threshold,); + +public: + static const std::map & defaultData(); + +public: + Statistics(); + Statistics(const Statistics & s); + virtual ~Statistics(); + + // name format = "Grp/Name/unit" + void addStatistic(const std::string & name, float value); + + // setters + void setExtended(bool extended) {_extended = extended;} + void setRefImageId(int refImageId) {_refImageId = refImageId;} + void setLoopClosureId(int loopClosureId) {_loopClosureId = loopClosureId;} + void setActions(const std::list > & actions) {_actions = actions;} + void setRefImage(IplImage ** refImage); + void setRefImage(const IplImage * refImage); + void setLoopClosureImage(IplImage ** loopClosureImage); + void setLoopClosureImage(const IplImage * loopClosureImage); + void setWeights(const std::map & weights) {_weights = weights;} + void setPosterior(const std::map & posterior) {_posterior = posterior;} + void setLikelihood(const std::map & likelihood) {_likelihood = likelihood;} + void setRefWords(const std::multimap & refWords) {_refWords = refWords;} + void setLoopWords(const std::multimap & loopWords) {_loopWords = loopWords;} + + // getters + bool extended() const {return _extended;} + int refImageId() const {return _refImageId;} + int loopClosureId() const {return _loopClosureId;} + const std::list > & getActions() const {return _actions;} + const IplImage * refImage() const {return _refImage;} + const IplImage * loopClosureImage() const {return _loopClosureImage;} + const std::map & weights() const {return _weights;} + const std::map & posterior() const {return _posterior;} + const std::map & likelihood() const {return _likelihood;} + const std::multimap & refWords() const {return _refWords;} + const std::multimap & loopWords() const {return _loopWords;} + const std::map & data() const {return _data;} + + + Statistics & operator=(const Statistics & s); + +private: + int _extended; // 0 -> only loop closure and last signature ID fields are filled + + int _refImageId; + int _loopClosureId; + + std::list > _actions; + + // extended data start here... + IplImage * _refImage; // Released by the event destructor + IplImage * _loopClosureImage; // Released by the event destructor + + std::map _weights; + std::map _posterior; + std::map _likelihood; + + //surf + std::multimap _refWords; + std::multimap _loopWords; + + // Format for statistics (Plottable statistics must go in that map) : + // {"Group/Name/Unit", value} + // Example : {"Timing/Total time/ms", 500.0f} + std::map _data; + static std::map _defaultData; + static bool _defaultDataInitialized; + // end extended data +}; + + +////////// The RtabmapEvent class ////////////// +class RtabmapEvent : public UEvent +{ +public: + RtabmapEvent(Statistics ** stats) : + UEvent(0), + _stats(*stats) {} + + virtual ~RtabmapEvent() {if(_stats) delete _stats;} + const Statistics & getStats() const {return *_stats;} + virtual std::string getClassName() const {return std::string("RtabmapEvent");} + +private: + Statistics * _stats; +}; + +class RtabmapEventCmd : public UEvent +{ +public: + enum Cmd { + kCmdResetMemory, + kCmdDumpMemory, + kCmdDumpPrediction, + kCmdGenerateGraph, + kCmdDeleteMemory}; +public: + RtabmapEventCmd(Cmd cmd) : + UEvent(0), + _cmd(cmd) {} + + virtual ~RtabmapEventCmd() {} + Cmd getCmd() const {return _cmd;} + void setStr(const std::string & str) {_str = str;} + const std::string & getStr() const {return _str;} + virtual std::string getClassName() const {return std::string("RtabmapEventCmd");} + +private: + Cmd _cmd; + std::string _str; +}; + +class RtabmapEventInit : public UEvent +{ +public: + enum Status { + kInitializing, + kInitialized, + kInfo, + kError + }; + +public: + RtabmapEventInit(Status status, const std::string & info = std::string()) : + UEvent(0), + _status(status), + _info(info) + {} + + // for convenience + RtabmapEventInit(const std::string & info) : + UEvent(0), + _status(kInfo), + _info(info) + {} + + Status getStatus() const {return _status;} + const std::string & getInfo() const {return _info;} + + virtual ~RtabmapEventInit() {} + virtual std::string getClassName() const {return std::string("RtabmapEventInit");} +private: + Status _status; + std::string _info; // "Loading signatures", "Loading words" ... +}; + +} // namespace rtabmap + +#endif /* RTABMAPEVENT_H_ */ diff --git a/corelib/include/rtabmap/core/RtabmapExp.h b/corelib/include/rtabmap/core/RtabmapExp.h new file mode 100644 index 0000000000..1da61eb7b8 --- /dev/null +++ b/corelib/include/rtabmap/core/RtabmapExp.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef RTABMAPEXP_H +#define RTABMAPEXP_H + +#ifdef WIN32 + #ifdef RTABMAP_EXPORTS + #define RTABMAP_EXP __declspec( dllexport ) + #else + #ifdef RTABMAP_EXPORTS_STATIC + #define RTABMAP_EXP + #else + #define RTABMAP_EXP __declspec( dllimport ) + #endif + #endif +#else + #define RTABMAP_EXP +#endif + +#endif // RTABMAPEXP_H diff --git a/corelib/include/rtabmap/core/SMState.h b/corelib/include/rtabmap/core/SMState.h new file mode 100644 index 0000000000..6c1a341321 --- /dev/null +++ b/corelib/include/rtabmap/core/SMState.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef SMPAIRVARIANT_H_ +#define SMPAIRVARIANT_H_ + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include +#include +#include +#include + +namespace rtabmap { + +// SensoriMotor state +class SMState +{ +public: + // Constructor 1 + // image and/or keypoints can be passed for debugging (rtabmap will not re-extract keypoints/descriptors from the image if not null, only for debug/visualization) + // take image ownership + SMState(const std::list > & sensorStates, const std::list > & actuatorStates, IplImage * image = 0, const std::list & keypoints = std::list()) : + _sensorStates(sensorStates), + _actuatorStates(actuatorStates), + _image(image), + _keypoints(keypoints), + _descriptorsProvided(true) + {} + // Constructor 2 : + // rtabmap will automatically extract keypoints and descriptors from the image... + // take image ownership + SMState(IplImage * image, const std::list > & actuatorStates = std::list >()) : + _actuatorStates(actuatorStates), + _image(image), + _descriptorsProvided(false) + {} + virtual ~SMState() + { + if(_image) + cvReleaseImage(&_image); + } + + bool isDescriptorsProvided() const {return _descriptorsProvided;} + const IplImage * getImage() const {return _image;} + const std::list & getKeypoints() const {return _keypoints;} + const std::list > & getSensorStates() const {return _sensorStates;} + const std::list > & getActuatorStates() const {return _actuatorStates;} + + void setSensorStates(const std::list > & sensorStates) {_sensorStates=sensorStates;} + void setActuatorStates(const std::list > & actuatorStates) {_actuatorStates=actuatorStates;} + +private: + std::list > _sensorStates; // descriptors + std::list > _actuatorStates; + IplImage * _image; + std::list _keypoints; + bool _descriptorsProvided; +}; + +// Sensorimotor state event +// Take ownership of the state +class SMStateEvent : public UEvent +{ +public: + SMStateEvent(SMState * state) : + UEvent(0), + _state(state) {} + + virtual ~SMStateEvent() {if(_state) delete _state;} + const SMState * getData() const {return _state;} + SMState * getDataOwnership() {SMState * state = _state; _state=0; return state;} + virtual std::string getClassName() const {return "SMStateEvent";} // TODO : macro? + +private: + SMState * _state; +}; + +} + +#endif /* SMPAIRVARIANT_H_ */ diff --git a/corelib/src/BayesFilter.cpp b/corelib/src/BayesFilter.cpp new file mode 100644 index 0000000000..09b7595438 --- /dev/null +++ b/corelib/src/BayesFilter.cpp @@ -0,0 +1,462 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "BayesFilter.h" +#include "Memory.h" +#include "Signature.h" +#include "rtabmap/core/Parameters.h" + +#include "utilite/UtiLite.h" + +namespace rtabmap { + +BayesFilter::BayesFilter(const ParametersMap & parameters) : + _virtualPlacePrior(Parameters::defaultBayesVirtualPlacePriorThr()) +{ + this->setPredictionLC(Parameters::defaultBayesPredictionLC()); + this->parseParameters(parameters); +} + +BayesFilter::~BayesFilter() { +} + +void BayesFilter::parseParameters(const ParametersMap & parameters) +{ + ParametersMap::const_iterator iter; + if((iter=parameters.find(Parameters::kBayesVirtualPlacePriorThr())) != parameters.end()) + { + this->setVirtualPlacePrior(std::atof((*iter).second.c_str())); + } + if((iter=parameters.find(Parameters::kBayesPredictionLC())) != parameters.end()) + { + this->setPredictionLC((*iter).second); + } +} + +void BayesFilter::setVirtualPlacePrior(float virtualPlacePrior) +{ + if(virtualPlacePrior < 0) + { + ULOGGER_WARN("virtualPlacePrior=%f, must be >=0 and <=1", virtualPlacePrior); + _virtualPlacePrior = 0; + } + else if(virtualPlacePrior > 1) + { + ULOGGER_WARN("virtualPlacePrior=%f, must be >=0 and <=1", virtualPlacePrior); + _virtualPlacePrior = 1; + } + else + { + _virtualPlacePrior = virtualPlacePrior; + } +} + +// format = {Virtual place, Loop closure, level1, level2, l3, l4...} +void BayesFilter::setPredictionLC(const std::string & prediction) +{ + std::list strValues = uSplit(prediction, ' '); + if(strValues.size() < 2) + { + ULOGGER_ERROR("The number of values < 2 (prediction=\"%s\")", prediction.c_str()); + } + else + { + std::vector tmpValues(strValues.size()); + int i=0; + bool valid = true; + float sum = 0;; + for(std::list::iterator iter = strValues.begin(); iter!=strValues.end(); ++iter) + { + tmpValues[i] = std::atof((*iter).c_str()); + sum += tmpValues[i]; + if(i>1) + { + sum += tmpValues[i]; // add a second time + } + if(tmpValues[i] < 0 || tmpValues[i]>1) + { + valid = false; + break; + } + ++i; + } + + if(!valid || sum <= 0 || sum > 1.001) + { + ULOGGER_ERROR("The prediction is not valid (the sum must be between >0 && <=1, sum=%f), negative values are not allowed (prediction=\"%s\")", sum, prediction.c_str()); + } + else + { + _predictionLC = tmpValues; + } + } +} + +const std::vector & BayesFilter::getPredictionLC() const +{ + // {Vp, Lc, l1, l2, l3, l4...} + return _predictionLC; +} + +std::string BayesFilter::getPredictionLCStr() const +{ + std::string values; + for(unsigned int i=0; i<_predictionLC.size(); ++i) + { + values.append(uNumber2str(_predictionLC[i])); + if(i+1 < _predictionLC.size()) + { + values.append(" "); + } + } + return values; +} + +void BayesFilter::reset() +{ + _posterior.clear(); +} + +const std::map & BayesFilter::computePosterior(const Memory * memory, const std::map & likelihood) +{ + ULOGGER_DEBUG(""); + + if(!memory) + { + ULOGGER_ERROR("Memory is Null!"); + return _posterior; + } + + if(!likelihood.size()) + { + ULOGGER_ERROR("likelihood is empty!"); + return _posterior; + } + + if(_predictionLC.size() < 2) + { + ULOGGER_ERROR("Prediction is not valid!"); + return _posterior; + } + + UTimer timer; + timer.start(); + + CvMat * prediction = 0; + CvMat * prior = 0; + CvMat * posterior = 0; + + float sum = 0; + int j=0; + // Recursive Bayes estimation... + // STEP 1 - Prediction : Prior*lastPosterior + prediction = cvCreateMat(likelihood.size(), likelihood.size(), CV_32FC1); + std::map likelihoodKeys; + int index = 0; + for(std::map::const_iterator iter=likelihood.begin(); iter!=likelihood.end(); ++iter) + { + likelihoodKeys.insert(likelihoodKeys.end(), std::pair(iter->first, index++)); + } + + if(this->generatePrediction(prediction, memory, likelihoodKeys)) + { + ULOGGER_DEBUG("STEP1-generate prior=%fs, rows=%d, cols=%d", timer.ticks(), prediction->rows, prediction->cols); + + // Adjust the last posterior if some images were + // reactivated or removed from the working memory + posterior = cvCreateMat(likelihood.size(), 1, CV_32FC1); + this->updatePosterior(memory, uKeys(likelihood)); + j=0; + for(std::map::const_iterator i=_posterior.begin(); i!= _posterior.end(); ++i) + { + posterior->data.fl[j++] = (*i).second; + } + ULOGGER_DEBUG("STEP1-update posterior=%fs, posterior=%d, _posterior size=%d", posterior->rows, _posterior.size()); + + // Multiply prediction matrix with the last posterior + // (m,m) X (m,1) = (m,1) + prior = cvCreateMat(likelihood.size(), 1, CV_32FC1); + cvMatMul(prediction, posterior, prior); + ULOGGER_DEBUG("STEP1-matrix mult time=%fs", timer.ticks()); + + // STEP 2 - Update : Multiply with observations (likelihood) + j=0; + for(std::map::const_iterator i=likelihood.begin(); i!= likelihood.end(); ++i) + { + std::map::iterator p =_posterior.find((*i).first); + if(p!= _posterior.end()) + { + (*p).second = (*i).second * prior->data.fl[j++]; + sum+=(*p).second; + } + else + { + ULOGGER_ERROR("Problem1! can't find id=%d", (*i).first); + } + } + ULOGGER_DEBUG("STEP2-likelihood time=%fs", timer.ticks()); + + // Normalize + ULOGGER_DEBUG("sum=%f", sum); + if(sum != 0) + { + for(std::map::iterator i=_posterior.begin(); i!= _posterior.end(); ++i) + { + (*i).second /= sum; + } + } + ULOGGER_DEBUG("normalize time=%fs", timer.ticks()); + } + + cvReleaseMat(&prediction); + cvReleaseMat(&prior); + cvReleaseMat(&posterior); + + return _posterior; +} + +bool BayesFilter::generatePrediction(CvMat * prediction, const Memory * memory, const std::map & likelihoodIds) const +{ + ULOGGER_DEBUG(""); + UTimer timer; + timer.start(); + UTimer timerGlobal; + timerGlobal.start(); + + if(!likelihoodIds.size() || + prediction == 0 || + prediction->rows != prediction->cols || + (unsigned int)prediction->rows != likelihoodIds.size()/*|| + prediction->type != CV_32FC1*/ || + _predictionLC.size() < 2 || + !memory) + { + ULOGGER_ERROR( "fail"); + return false; + } + + //int rows = prediction->rows; + cvSetZero(prediction); + int cols = prediction->cols; + + // Each priors are column vectors + unsigned int i=0; + ULOGGER_DEBUG("_predictionLC.size()=%d",_predictionLC.size()); + for(std::map::const_iterator iter=likelihoodIds.begin(); iter!=likelihoodIds.end(); ++iter) + { + if(iter->first > 0) + { + // Create the sum of 2 gaussians around the loop closure + + int loopClosureId = iter->first; + + // Set high values (gaussians curves) to loop closure neighbors + const Signature * loopSign = memory->getSignature(loopClosureId); + if(!loopSign) + { + ULOGGER_ERROR("loopSign %d is not found?!?", loopClosureId); + } + + // LoopID + prediction->data.fl[i + i*cols] += _predictionLC[1]; + + // look up for each neighbors (RECURSIVE) + this->addNeighborProb(prediction, i, memory, likelihoodIds, loopSign, 1); + + //ULOGGER_DEBUG("neighbor prob for %d, neighbors=%d, time = %fs", loopSign->id(), loopSign->getNeighborIds().size(), timer.ticks()); + + float totalModelValues = _predictionLC[0] + _predictionLC[1]; + for(unsigned int j=2; j<_predictionLC.size(); ++j) + { + totalModelValues += _predictionLC[j]*2; + } + + //Add values of not found neighbors to the loop closure + float sum = 0; + for(int j=0; jdata.fl[i + j*cols]; + } + if(sum < (totalModelValues-_predictionLC[0])) + { + float gap = (totalModelValues-_predictionLC[0]) - sum; + prediction->data.fl[i + i*cols] += gap; + sum += gap; + } + + // add virtual place prob + if(likelihoodIds.begin()->first < 0) + { + sum += prediction->data.fl[i] = _predictionLC[0]; + } + + // Set all loop events to small values according to the model + if(totalModelValues < 1.0f) + { + float value = (1.0f-totalModelValues) / float(cols); + for(int j=0; jdata.fl[i + j*cols]) + { + sum += prediction->data.fl[i + j*cols] = value; + } + } + } + + //normalize this row, + for(int j=0; jdata.fl[i + j*cols] /= sum; + } + + //debug + //for(int j=0; jdata.fl[i + j*cols]); + //} + } + else + { + // Set the virtual place prior + if(_virtualPlacePrior > 0) + { + if(cols>1) // The first must be the virtual place + { + prediction->data.fl[i] = _virtualPlacePrior; + float val = (1.0-_virtualPlacePrior)/(cols-1); + for(int j=1; jdata.fl[i + j*cols] = val; + } + } + else if(cols>0) + { + prediction->data.fl[i] = 1; + } + } + else + { + // Only for some tests... + // when _virtualPlacePrior=0, set all priors to the same value + if(cols>1) + { + float val = 1.0/cols; + for(int j=0; jdata.fl[i + j*cols] = val; + } + } + else if(cols>0) + { + prediction->data.fl[i] = 1; + } + } + } + ++i; + } + + ULOGGER_DEBUG("time = %fs", timerGlobal.ticks()); + + return true; +} + +void BayesFilter::updatePosterior(const Memory * memory, const std::vector & likelihoodIds) +{ + ULOGGER_DEBUG(""); + std::map newPosterior; + for(std::vector::const_iterator i=likelihoodIds.begin(); i != likelihoodIds.end(); ++i) + { + std::map::iterator post = _posterior.find(*i); + if(post == _posterior.end()) + { + if(_posterior.size() == 0) + { + newPosterior.insert(std::pair(*i, 1)); + } + else + { + newPosterior.insert(std::pair(*i, 0)); + } + } + else + { + newPosterior.insert(std::pair((*post).first, (*post).second)); + } + } + _posterior = newPosterior; +} + +//recursive... +float BayesFilter::addNeighborProb(CvMat * prediction, unsigned int row, const Memory * memory, const std::map & likelihoodIds, const Signature * s, unsigned int level) const +{ + if(!likelihoodIds.size() || + prediction == 0 || + prediction->rows != prediction->cols || + (unsigned int)prediction->rows != likelihoodIds.size() || + _predictionLC.size() < 2 || + !memory || + !prediction || + level<1) + { + ULOGGER_ERROR( "fail"); + return 0; + } + + if(level+1 >= _predictionLC.size() || !s) + { + return 0; + } + + double value = _predictionLC[level+1]; + float sum=0; + + const NeighborsMap & neighbors = s->getNeighbors(); + for(NeighborsMap::const_iterator iter=neighbors.begin(); iter!= neighbors.end(); ++iter) + { + int index = uValue(likelihoodIds, iter->first, -1); + if(index >= 0) + { + bool alreadyAdded = false; + // the value can be already added in the recursion + if(value > prediction->data.fl[row + index*prediction->cols]) + { + sum -= prediction->data.fl[row + index*prediction->cols]; + prediction->data.fl[row + index*prediction->cols] = value; + sum += value; + } + else + { + alreadyAdded = true; + } + + if(!alreadyAdded && level+1 < _predictionLC.size()) + { + sum += addNeighborProb(prediction, row, memory, likelihoodIds, memory->getSignature(iter->first), level+1); + } + } + else + { + //ULOGGER_DEBUG("BayesFilter::generatePrediction(...) F (id %d) Not found for loop %d", loopSign->getNeighborForward(), loopClosureId); + } + } + return sum; +} + + +} // namespace rtabmap diff --git a/corelib/src/BayesFilter.h b/corelib/src/BayesFilter.h new file mode 100644 index 0000000000..ded2979367 --- /dev/null +++ b/corelib/src/BayesFilter.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef BAYESFILTER_H_ +#define BAYESFILTER_H_ + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include +#include +#include +#include "utilite/UEventsHandler.h" +#include "rtabmap/core/Parameters.h" + +namespace rtabmap { + +class Memory; +class Signature; + +class RTABMAP_EXP BayesFilter +{ +public: + BayesFilter(const ParametersMap & parameters = ParametersMap()); + virtual ~BayesFilter(); + virtual void parseParameters(const ParametersMap & parameters); + const std::map & computePosterior(const Memory * memory, const std::map & likelihood); + void reset(); + + //setters + void setVirtualPlacePrior(float virtualPlacePrior); + void setPredictionLC(const std::string & prediction); + + //getters + const std::map & getPosterior() const {return _posterior;} + float getVirtualPlacePrior() const {return _virtualPlacePrior;} + const std::vector & getPredictionLC() const; // {Vp, Lc, l1, l2, l3, l4...} + std::string getPredictionLCStr() const; // for convenience {Vp, Lc, l1, l2, l3, l4...} + + bool generatePrediction(CvMat * prediction, const Memory * memory, const std::map & likelihoodIds) const; + float addNeighborProb(CvMat * prediction, unsigned int row, const Memory * memory, const std::map & likelihoodIds, const Signature * s, unsigned int level) const; + +private: + void updatePosterior(const Memory * memory, const std::vector & likelihoodIds); + +private: + std::map _posterior; + float _virtualPlacePrior; + std::vector _predictionLC; // {Vp, Lc, l1, l2, l3, l4...} +}; + +} // namespace rtabmap + +#endif /* BAYESFILTER_H_ */ diff --git a/corelib/src/CMakeLists.txt b/corelib/src/CMakeLists.txt new file mode 100644 index 0000000000..f211324955 --- /dev/null +++ b/corelib/src/CMakeLists.txt @@ -0,0 +1,77 @@ + +SET(SRC_FILES + Rtabmap.cpp + RtabmapEvent.cpp + + Memory.cpp + KeypointMemory.cpp + + DBDriverFactory.cpp + DBDriver.cpp + DBDriverSqlite3.cpp + + Camera.cpp + EpipolarGeometry.cpp + VisualWord.cpp + VWDictionary.cpp + BayesFilter.cpp + Parameters.cpp + Signature.cpp + KeypointDetector.cpp + KeypointDescriptor.cpp + VerifyHypotheses.cpp + NearestNeighbor.cpp +) + +SET(INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/../include + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${UTILITE_INCLUDE_DIR} + ${OpenCV_INCLUDE_DIRS} + ${SQLITE3_INCLUDE_DIR} +) + +SET(LIBRARIES + ${UTILITE_LIBRARY} + ${OpenCV_LIBS} + ${SQLITE3_LIBRARY} +) + +# Generate resources files +ADD_CUSTOM_COMMAND( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/DatabaseSchema_sql.h + COMMAND ${URESOURCEGENERATOR_EXEC} -n rtabmap -p ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/resources/DatabaseSchema.sql + COMMENT "[Creating resources]" +) + +# Make sure the compiler can find include files from our library. +INCLUDE_DIRECTORIES(${INCLUDE_DIRS}) + +IF(WIN32) + IF(BUILD_SHARED_LIBS) + ADD_DEFINITIONS(-DRTABMAP_EXPORTS) + ELSE() + ADD_DEFINITIONS(-DRTABMAP_EXPORTS_STATIC) + ENDIF() +ENDIF(WIN32) + +# Add binary that is built from the source file "main.cpp". +# The extension is automatically found. +ADD_LIBRARY(corelib ${SRC_FILES} ${CMAKE_CURRENT_BINARY_DIR}/DatabaseSchema_sql.h) +TARGET_LINK_LIBRARIES(corelib ${LIBRARIES}) + +SET_TARGET_PROPERTIES( +corelib +PROPERTIES + OUTPUT_NAME ${PROJECT_PREFIX}_core + INSTALL_NAME_DIR ${CMAKE_INSTALL_PREFIX}/lib +) + +INSTALL(TARGETS corelib + RUNTIME DESTINATION bin COMPONENT runtime + LIBRARY DESTINATION lib COMPONENT devel + ARCHIVE DESTINATION lib COMPONENT devel) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../include/ DESTINATION include/ COMPONENT devel FILES_MATCHING PATTERN "*.h" PATTERN ".svn" EXCLUDE) + diff --git a/corelib/src/Camera.cpp b/corelib/src/Camera.cpp new file mode 100644 index 0000000000..1b1c57e0d0 --- /dev/null +++ b/corelib/src/Camera.cpp @@ -0,0 +1,651 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "rtabmap/core/Camera.h" +#include "rtabmap/core/CameraEvent.h" +#include "utilite/UEventsManager.h" +#include "utilite/UConversion.h" +#include "rtabmap/core/DBDriver.h" +#include "rtabmap/core/DBDriverFactory.h" +#include "rtabmap/core/KeypointDescriptor.h" +#include "rtabmap/core/KeypointDetector.h" +#include "rtabmap/core/SMState.h" +#include "utilite/UStl.h" +#include "utilite/UConversion.h" +#include "utilite/UFile.h" +#include "utilite/UDirectory.h" +#include "utilite/UTimer.h" +#include + +namespace rtabmap +{ + +SMState * CamPostTreatment::process(IplImage * image) +{ + if(image) + { + return new SMState(image); + } + return 0; +} + +CamKeypointTreatment::~CamKeypointTreatment() +{ + if(_keypointDetector) + { + delete _keypointDetector; + } + if(_keypointDescriptor) + { + delete _keypointDescriptor; + } +} +SMState * CamKeypointTreatment::process(IplImage * image) +{ + if(image) + { + std::list keypoints = _keypointDetector->generateKeypoints(image); + std::list > descriptors = _keypointDescriptor->generateDescriptors(image, keypoints); + SMState * smState = new SMState(descriptors, std::list >(), image, keypoints); + return smState; + } + return 0; +} + +void CamKeypointTreatment::parseParameters(const ParametersMap & parameters) +{ + UDEBUG(""); + ParametersMap::const_iterator iter; + //Keypoint detector + DetectorStrategy detectorStrategy = kDetectorUndef; + if((iter=parameters.find(Parameters::kKpDetectorStrategy())) != parameters.end()) + { + detectorStrategy = (DetectorStrategy)std::atoi((*iter).second.c_str()); + } + DetectorStrategy currentDetectorStrategy = this->detectorStrategy(); + if(!_keypointDetector || ( detectorStrategy!=kDetectorUndef && (detectorStrategy != currentDetectorStrategy) ) ) + { + ULOGGER_DEBUG("new detector strategy %d", int(detectorStrategy)); + if(_keypointDetector) + { + delete _keypointDetector; + _keypointDetector = 0; + } + switch(detectorStrategy) + { + case kDetectorStar: + _keypointDetector = new StarDetector(parameters); + break; + case kDetectorSift: + _keypointDetector = new SIFTDetector(parameters); + break; + case kDetectorSurf: + default: + _keypointDetector = new SURFDetector(parameters); + break; + } + } + else if(_keypointDetector) + { + _keypointDetector->parseParameters(parameters); + } + + //Keypoint descriptor + DescriptorStrategy descriptorStrategy = kDescriptorUndef; + if((iter=parameters.find(Parameters::kKpDescriptorStrategy())) != parameters.end()) + { + descriptorStrategy = (DescriptorStrategy)std::atoi((*iter).second.c_str()); + } + if(!_keypointDescriptor || descriptorStrategy!=kDescriptorUndef) + { + ULOGGER_DEBUG("new descriptor strategy %d", int(descriptorStrategy)); + if(_keypointDescriptor) + { + delete _keypointDescriptor; + _keypointDescriptor = 0; + } + switch(descriptorStrategy) + { + case kDescriptorColorSurf: + // see decorator pattern... + _keypointDescriptor = new ColorDescriptor(parameters, new SURFDescriptor(parameters)); + break; + case kDescriptorLaplacianSurf: + // see decorator pattern... + _keypointDescriptor = new LaplacianDescriptor(parameters, new SURFDescriptor(parameters)); + break; + case kDescriptorSift: + _keypointDescriptor = new SIFTDescriptor(parameters); + break; + case kDescriptorHueSurf: + // see decorator pattern... + _keypointDescriptor = new HueDescriptor(parameters, new SURFDescriptor(parameters)); + break; + case kDescriptorSurf: + default: + _keypointDescriptor = new SURFDescriptor(parameters); + break; + } + } + else if(_keypointDescriptor) + { + _keypointDescriptor->parseParameters(parameters); + } + CamPostTreatment::parseParameters(parameters); +} + +CamKeypointTreatment::DetectorStrategy CamKeypointTreatment::detectorStrategy() const +{ + DetectorStrategy strategy = kDetectorUndef; + StarDetector * star = dynamic_cast(_keypointDetector); + SURFDetector * surf = dynamic_cast(_keypointDetector); + if(star) + { + strategy = kDetectorStar; + } + else if(surf) + { + strategy = kDetectorSurf; + } + return strategy; +} + +Camera::Camera(float imageRate, + bool autoRestart, + unsigned int imageWidth, + unsigned int imageHeight) : + _imageRate(imageRate), + _autoRestart(autoRestart), + _imageWidth(imageWidth), + _imageHeight(imageHeight) +{ + _postThreatement = new CamPostTreatment(); + UEventsManager::addHandler(this); +} + +Camera::~Camera(void) +{ + this->kill(); + delete _postThreatement; +} + +void Camera::mainLoop() +{ + State state = kStateCapturing; + ParametersMap parameters; + + _stateMutex.lock(); + { + if(!_state.empty() && !_stateParam.empty()) + { + state = _state.top(); + _state.pop(); + parameters = _stateParam.top(); + _stateParam.pop(); + } + } + _stateMutex.unlock(); + + if(state == kStateCapturing) + { + process(); + } + else if(state == kStateChangingParameters) + { + this->parseParameters(parameters); + } +} + +// ownership is transferred +void Camera::setPostThreatement(CamPostTreatment * strategy) +{ + if(strategy) + { + delete _postThreatement; + _postThreatement = strategy; + } +} + +void Camera::pushNewState(State newState, const ParametersMap & parameters) +{ + ULOGGER_DEBUG("to %d", newState); + + _stateMutex.lock(); + { + _state.push(newState); + _stateParam.push(parameters); + } + _stateMutex.unlock(); +} + +void Camera::handleEvent(UEvent* anEvent) +{ + if(anEvent->getClassName().compare("CameraEvent") == 0) + { + CameraEvent * cameraEvent = (CameraEvent*)anEvent; + if(cameraEvent->getCode() == CameraEvent::kCodeCtrl) + { + CameraEvent::Cmd cmd = cameraEvent->getCommand(); + + if(cmd == CameraEvent::kCmdPause) + { + if(this->isRunning()) + { + this->kill(); + } + else + { + this->start(); + } + } + else if(cmd == CameraEvent::kCmdChangeParam) + { + // TODO : Put in global Parameters ? + _imageRate = cameraEvent->getImageRate(); + _autoRestart = cameraEvent->getAutoRestart(); + } + else + { + ULOGGER_DEBUG("Camera::handleEvent(Util::Event* anEvent) : command undefined..."); + } + } + } + if(anEvent->getClassName().compare("ParamEvent") == 0) + { + if(this->isIdle()) + { + _stateMutex.lock(); + this->parseParameters(((ParamEvent*)anEvent)->getParameters()); + _stateMutex.unlock(); + } + else + { + ULOGGER_DEBUG("changing parameters"); + pushNewState(kStateChangingParameters, ((ParamEvent*)anEvent)->getParameters()); + } + } +} + +void Camera::process() +{ + UTimer timer; + ULOGGER_DEBUG("Camera::process()"); + IplImage * image = this->takeImage(); + if(image) + { + SMState * smState = _postThreatement->process(image); + this->post(new SMStateEvent(smState)); + double elapsed = timer.ticks(); + UDEBUG("Post treatment time = %fs", elapsed); + if(_imageRate>0) + { + float sleepTime = 1000.0f/_imageRate - 1000.0f*elapsed; + if(sleepTime > 0) + { + UDEBUG("Now sleeping for = %fms", sleepTime); + uSleep(sleepTime); + } + } + } + else + { + if(_autoRestart) + { + this->init(); + } + else + { + ULOGGER_DEBUG("Camera::process() : no more images..."); + this->kill(); + this->post(new CameraEvent()); + } + } +} + + +///////////////////////// +// CameraImages +///////////////////////// +CameraImages::CameraImages(const std::string & path, + int startAt, + bool refreshDir, + float imageRate, + bool autoRestart, + unsigned int imageWidth, + unsigned int imageHeight) : + Camera(imageRate, autoRestart, imageWidth, imageHeight), + _path(path), + _startAt(startAt), + _refreshDir(refreshDir), + _dir(0), + _count(0) +{ +} + +CameraImages::~CameraImages(void) +{ + this->kill(); + if(_dir) + { + delete _dir; + _dir = 0; + } +} + +bool CameraImages::init() +{ + if(_dir) + { + delete _dir; + _dir = 0; + } + _dir = new UDirectory(_path, "jpg ppm png bmp"); + _count = 0; + if(_path[_path.size()-1] != '\\' && _path[_path.size()-1] != '/') + { + _path.append("/"); + } + if(!_dir) + { + ULOGGER_ERROR("Directory path not valid \"%s\"", _path.c_str()); + } + return _dir != 0; +} + +IplImage * CameraImages::takeImage() +{ + IplImage * img = 0; + if(_dir) + { + if(_refreshDir) + { + _dir->update(); + } + if(_startAt == 0) + { + const std::list & fileNames = _dir->getFileNames(); + if(fileNames.size()) + { + if(_lastFileName.empty() || uStrNumCmp(_lastFileName,*fileNames.rbegin()) < 0) + { + _lastFileName = *fileNames.rbegin(); + std::string fullPath = _path + _lastFileName; + img = cvLoadImage(fullPath.c_str(), CV_LOAD_IMAGE_UNCHANGED); + } + } + } + else + { + std::string fileName; + std::string fullPath; + fileName = _dir->getNextFileName(); + if(fileName.size()) + { + fullPath = _path + fileName; + while(++_count < _startAt && (fileName = _dir->getNextFileName()).size()) + { + fullPath = _path + fileName; + } + if(fileName.size()) + { + ULOGGER_DEBUG("Loading image : %s\n", fullPath.c_str()); + img = cvLoadImage(fullPath.c_str(), CV_LOAD_IMAGE_UNCHANGED); + } + } + } + } + if(img && + getImageWidth() && + getImageHeight() && + getImageWidth() != (unsigned int)img->width && + getImageHeight() != (unsigned int)img->height) + { + // declare a destination IplImage object with correct size, depth and channels + IplImage * resampledImg = cvCreateImage( cvSize((int)(getImageWidth()) , + (int)(getImageHeight()) ), + img->depth, img->nChannels ); + + //use cvResize to resize source to a destination image (linear interpolation) + cvResize(img, resampledImg); + cvReleaseImage(&img); + img = resampledImg; + } + return img; +} + + + +///////////////////////// +// CameraVideo +///////////////////////// +CameraVideo::CameraVideo(int usbDevice, + float imageRate, + bool autoRestart, + unsigned int imageWidth, + unsigned int imageHeight) : + Camera(imageRate, autoRestart, imageWidth, imageHeight), + _capture(0), + _src(kUsbDevice), + _usbDevice(usbDevice) +{ + +} + +CameraVideo::CameraVideo(const std::string & fileName, + float imageRate, + bool autoRestart, + unsigned int imageWidth, + unsigned int imageHeight) : + Camera(imageRate, autoRestart, imageWidth, imageHeight), + _fileName(fileName), + _capture(0), + _src(kVideoFile) +{ +} + +CameraVideo::~CameraVideo() +{ + this->kill(); + if(_capture) + { + cvReleaseCapture(&_capture); + } +} + +bool CameraVideo::init() +{ + if(_capture) + { + cvReleaseCapture(&_capture); + _capture = 0; + } + + if(_src == kUsbDevice) + { + ULOGGER_DEBUG("CameraVideo::init() Usb device initialization on device %d with imgSize=[%d,%d]", _usbDevice, getImageWidth(), getImageHeight()); + _capture = cvCaptureFromCAM(_usbDevice); + if(_capture && getImageWidth() && getImageHeight()) + { + cvSetCaptureProperty(_capture, CV_CAP_PROP_FRAME_WIDTH, double(getImageWidth())); + cvSetCaptureProperty(_capture, CV_CAP_PROP_FRAME_HEIGHT, double(getImageHeight())); + } + } + else if(_src == kVideoFile) + { + ULOGGER_DEBUG("CameraVideo::init() filename=\"%s\"", _fileName.c_str()); + _capture = cvCaptureFromAVI(_fileName.c_str()); + } + else + { + ULOGGER_ERROR("CameraVideo::init() Unknown source..."); + } + if(!_capture) + { + ULOGGER_ERROR("CameraVideo::init() Failed to create a capture object!"); + return false; + } + return true; +} + +IplImage * CameraVideo::takeImage() +{ + IplImage * img = 0; // Null image + if(_capture) + { + if(!cvGrabFrame(_capture)){ // capture a frame + ULOGGER_WARN("CameraVideo: Could not grab a frame, the end of the feed may be reached..."); + } + else + { + img=cvRetrieveFrame(_capture); // retrieve the captured frame + } + } + else + { + ULOGGER_WARN("CameraVideo::takeImage() The camera must be initialized before requesting an image."); + } + + if(img && + getImageWidth() && + getImageHeight() && + getImageWidth() != (unsigned int)img->width && + getImageHeight() != (unsigned int)img->height) + { + // declare a destination IplImage object with correct size, depth and channels + IplImage * resampledImg = cvCreateImage( cvSize((int)(getImageWidth()) , + (int)(getImageHeight()) ), + img->depth, img->nChannels ); + + //use cvResize to resize source to a destination image (linear interpolation) + cvResize(img, resampledImg); + img = resampledImg; + } + else if(img) + { + img = cvCloneImage(img); + } + + return img; +} + + + + + + + +///////////////////////// +// CameraDatabase +///////////////////////// +CameraDatabase::CameraDatabase(const std::string & path, + bool ignoreChildren, + float imageRate, + bool autoRestart, + unsigned int imageWidth, + unsigned int imageHeight) : + Camera(imageRate, autoRestart, imageWidth, imageHeight), + _path(path), + _ignoreChildren(ignoreChildren), + _indexIter(_ids.begin()), + _dbDriver(0) +{ +} + +CameraDatabase::~CameraDatabase(void) +{ + this->kill(); + if(_dbDriver) + { + _dbDriver->closeConnection(); + delete _dbDriver; + } +} + +bool CameraDatabase::init() +{ + if(_dbDriver) + { + _dbDriver->closeConnection(); + delete _dbDriver; + _dbDriver = 0; + } + _ids.clear(); + _indexIter = _ids.begin(); + + std::string driverType = "sqlite3"; + ParametersMap parameters; + parameters.insert(ParametersPair(Parameters::kDbSqlite3InMemory(), "false")); + _dbDriver = rtabmap::DBDriverFactory::createDBDriver(driverType, parameters); + if(!_dbDriver) + { + ULOGGER_ERROR("CameraDatabase::init() can't create \"%s\" driver",driverType.c_str()); + return false; + } + else if(!_dbDriver->openConnection(_path.c_str())) + { + ULOGGER_ERROR("CameraDatabase::init() Can't read database \"%s\"",_path.c_str()); + return false; + } + else + { + // TODO load all signatures only if ignoreChildren is false + _dbDriver->getAllSignatureIds(_ids); + _indexIter = _ids.begin(); + } + return true; +} + +IplImage * CameraDatabase::takeImage() +{ + IplImage * img = 0; + if(_dbDriver && _indexIter != _ids.end()) + { + _dbDriver->getImage(*_indexIter, &img); + ++_indexIter; + } + else if(!_dbDriver) + { + ULOGGER_WARN("The camera must be initialized first..."); + } + else if(_ids.size() == 0) + { + ULOGGER_WARN("The database \"%s\" is empty...", _path.c_str()); + } + + if(img && + getImageWidth() && + getImageHeight() && + getImageWidth() != (unsigned int)img->width && + getImageHeight() != (unsigned int)img->height) + { + // declare a destination IplImage object with correct size, depth and channels + IplImage * resampledImg = cvCreateImage( cvSize((int)(getImageWidth()) , + (int)(getImageHeight()) ), + img->depth, img->nChannels ); + + //use cvResize to resize source to a destination image (linear interpolation) + cvResize(img, resampledImg); + cvReleaseImage(&img); + img = resampledImg; + } + + return img; +} + +} // namespace rtabmap diff --git a/corelib/src/ConvertUTF.c b/corelib/src/ConvertUTF.c new file mode 100644 index 0000000000..4351b153b7 --- /dev/null +++ b/corelib/src/ConvertUTF.c @@ -0,0 +1,539 @@ +/* + * Copyright 2001-2004 Unicode, Inc. + * + * Disclaimer + * + * This source code is provided as is by Unicode, Inc. No claims are + * made as to fitness for any particular purpose. No warranties of any + * kind are expressed or implied. The recipient agrees to determine + * applicability of information provided. If this file has been + * purchased on magnetic or optical media from Unicode, Inc., the + * sole remedy for any claim will be exchange of defective media + * within 90 days of receipt. + * + * Limitations on Rights to Redistribute This Code + * + * Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard, and to make copies of this file in any form + * for internal or external distribution as long as this notice + * remains attached. + */ + +/* --------------------------------------------------------------------- + + Conversions between UTF32, UTF-16, and UTF-8. Source code file. + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Sept 2001: fixed const & error conditions per + mods suggested by S. Parent & A. Lillich. + June 2002: Tim Dodd added detection and handling of incomplete + source sequences, enhanced error detection, added casts + to eliminate compiler warnings. + July 2003: slight mods to back out aggressive FFFE detection. + Jan 2004: updated switches in from-UTF8 conversions. + Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions. + + See the header file "ConvertUTF.h" for complete documentation. + +------------------------------------------------------------------------ */ + + +#include "ConvertUTF.h" +#ifdef CVTUTF_DEBUG +#include +#endif + +static const int halfShift = 10; /* used for shifting by 10 bits */ + +static const UTF32 halfBase = 0x0010000UL; +static const UTF32 halfMask = 0x3FFUL; + +#define UNI_SUR_HIGH_START (UTF32)0xD800 +#define UNI_SUR_HIGH_END (UTF32)0xDBFF +#define UNI_SUR_LOW_START (UTF32)0xDC00 +#define UNI_SUR_LOW_END (UTF32)0xDFFF +#define false 0 +#define true 1 + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF32toUTF16 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + if (target >= targetEnd) { + result = targetExhausted; break; + } + ch = *source++; + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = (UTF16)ch; /* normal case */ + } + } else if (ch > UNI_MAX_LEGAL_UTF32) { + if (flags == strictConversion) { + result = sourceIllegal; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + --source; /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF16toUTF32 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF32* target = *targetStart; + UTF32 ch, ch2; + while (source < sourceEnd) { + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + if (target >= targetEnd) { + source = oldSource; /* Back up source pointer! */ + result = targetExhausted; break; + } + *target++ = ch; + } + *sourceStart = source; + *targetStart = target; +#ifdef CVTUTF_DEBUG +if (result == sourceIllegal) { + fprintf(stderr, "ConvertUTF16toUTF32 illegal seq 0x%04x,%04x\n", ch, ch2); + fflush(stderr); +} +#endif + return result; +} + +/* --------------------------------------------------------------------- */ + +/* + * Index into the table below with the first byte of a UTF-8 sequence to + * get the number of trailing bytes that are supposed to follow it. + * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is + * left as-is for anyone who may want to do such conversion, which was + * allowed in earlier algorithms. + */ +static const char trailingBytesForUTF8[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 +}; + +/* + * Magic values subtracted from a buffer value during UTF8 conversion. + * This table contains as many values as there might be trailing bytes + * in a UTF-8 sequence. + */ +static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; + +/* + * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed + * into the first byte, depending on how many bytes follow. There are + * as many entries in this table as there are UTF-8 sequence types. + * (I.e., one byte sequence, two byte... etc.). Remember that sequencs + * for *legal* UTF-8 will be 4 or fewer bytes total. + */ +static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + +/* --------------------------------------------------------------------- */ + +/* The interface converts a whole buffer to avoid function-call overhead. + * Constants have been gathered. Loops & conditionals have been removed as + * much as possible for efficiency, in favor of drop-through switches. + * (See "Note A" at the bottom of the file for equivalent code.) + * If your compiler supports it, the "isLegalUTF8" call can be turned + * into an inline function. + */ + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF16toUTF8 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + UTF32 ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* Figure out how many bytes the result will require */ + if (ch < (UTF32)0x80) { bytesToWrite = 1; + } else if (ch < (UTF32)0x800) { bytesToWrite = 2; + } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; + } else if (ch < (UTF32)0x110000) { bytesToWrite = 4; + } else { bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + } + + target += bytesToWrite; + if (target > targetEnd) { + source = oldSource; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +/* + * Utility routine to tell whether a sequence of bytes is legal UTF-8. + * This must be called with the length pre-determined by the first byte. + * If not calling this from ConvertUTF8to*, then the length can be set by: + * length = trailingBytesForUTF8[*source]+1; + * and the sequence is illegal right away if there aren't that many bytes + * available. + * If presented with a length > 4, this returns false. The Unicode + * definition of UTF-8 goes up to 4-byte sequences. + */ + +static Boolean isLegalUTF8(const UTF8 *source, int length) { + UTF8 a; + const UTF8 *srcptr = source+length; + switch (length) { + default: return false; + /* Everything else falls through when "true"... */ + case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; + case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; + case 2: if ((a = (*--srcptr)) > 0xBF) return false; + + switch (*source) { + /* no fall-through in this inner switch */ + case 0xE0: if (a < 0xA0) return false; break; + case 0xED: if (a > 0x9F) return false; break; + case 0xF0: if (a < 0x90) return false; break; + case 0xF4: if (a > 0x8F) return false; break; + default: if (a < 0x80) return false; + } + + case 1: if (*source >= 0x80 && *source < 0xC2) return false; + } + if (*source > 0xF4) return false; + return true; +} + +/* --------------------------------------------------------------------- */ + +/* + * Exported function to return whether a UTF-8 sequence is legal or not. + * This is not used here; it's just exported. + */ +Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd) { + int length = trailingBytesForUTF8[*source]+1; + if (source+length > sourceEnd) { + return false; + } + return isLegalUTF8(source, length); +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF8toUTF16 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (! isLegalUTF8(source, extraBytesToRead+1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; + + if (target >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead+1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = (UTF16)ch; /* normal case */ + } + } else if (ch > UNI_MAX_UTF16) { + if (flags == strictConversion) { + result = sourceIllegal; + source -= (extraBytesToRead+1); /* return to the start */ + break; /* Bail out; shouldn't continue */ + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF32toUTF8 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + ch = *source++; + if (flags == strictConversion ) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* + * Figure out how many bytes the result will require. Turn any + * illegally large UTF32 things (> Plane 17) into replacement chars. + */ + if (ch < (UTF32)0x80) { bytesToWrite = 1; + } else if (ch < (UTF32)0x800) { bytesToWrite = 2; + } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; + } else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; + } else { bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + result = sourceIllegal; + } + + target += bytesToWrite; + if (target > targetEnd) { + --source; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF8toUTF32 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF32* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (! isLegalUTF8(source, extraBytesToRead+1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; + case 4: ch += *source++; ch <<= 6; + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; + + if (target >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up the source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_LEGAL_UTF32) { + /* + * UTF-16 surrogate values are illegal in UTF-32, and anything + * over Plane 17 (> 0x10FFFF) is illegal. + */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead+1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = ch; + } + } else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */ + result = sourceIllegal; + *target++ = UNI_REPLACEMENT_CHAR; + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- + + Note A. + The fall-through switches in UTF-8 reading code save a + temp variable, some decrements & conditionals. The switches + are equivalent to the following loop: + { + int tmpBytesToRead = extraBytesToRead+1; + do { + ch += *source++; + --tmpBytesToRead; + if (tmpBytesToRead) ch <<= 6; + } while (tmpBytesToRead > 0); + } + In UTF-8 writing code, the switches on "bytesToWrite" are + similarly unrolled loops. + + --------------------------------------------------------------------- */ diff --git a/corelib/src/ConvertUTF.h b/corelib/src/ConvertUTF.h new file mode 100644 index 0000000000..f1230ee994 --- /dev/null +++ b/corelib/src/ConvertUTF.h @@ -0,0 +1,149 @@ +/* + * Copyright 2001-2004 Unicode, Inc. + * + * Disclaimer + * + * This source code is provided as is by Unicode, Inc. No claims are + * made as to fitness for any particular purpose. No warranties of any + * kind are expressed or implied. The recipient agrees to determine + * applicability of information provided. If this file has been + * purchased on magnetic or optical media from Unicode, Inc., the + * sole remedy for any claim will be exchange of defective media + * within 90 days of receipt. + * + * Limitations on Rights to Redistribute This Code + * + * Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard, and to make copies of this file in any form + * for internal or external distribution as long as this notice + * remains attached. + */ + +/* --------------------------------------------------------------------- + + Conversions between UTF32, UTF-16, and UTF-8. Header file. + + Several funtions are included here, forming a complete set of + conversions between the three formats. UTF-7 is not included + here, but is handled in a separate source file. + + Each of these routines takes pointers to input buffers and output + buffers. The input buffers are const. + + Each routine converts the text between *sourceStart and sourceEnd, + putting the result into the buffer between *targetStart and + targetEnd. Note: the end pointers are *after* the last item: e.g. + *(sourceEnd - 1) is the last item. + + The return result indicates whether the conversion was successful, + and if not, whether the problem was in the source or target buffers. + (Only the first encountered problem is indicated.) + + After the conversion, *sourceStart and *targetStart are both + updated to point to the end of last text successfully converted in + the respective buffers. + + Input parameters: + sourceStart - pointer to a pointer to the source buffer. + The contents of this are modified on return so that + it points at the next thing to be converted. + targetStart - similarly, pointer to pointer to the target buffer. + sourceEnd, targetEnd - respectively pointers to the ends of the + two buffers, for overflow checking only. + + These conversion functions take a ConversionFlags argument. When this + flag is set to strict, both irregular sequences and isolated surrogates + will cause an error. When the flag is set to lenient, both irregular + sequences and isolated surrogates are converted. + + Whether the flag is strict or lenient, all illegal sequences will cause + an error return. This includes sequences such as: , , + or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code + must check for illegal sequences. + + When the flag is set to lenient, characters over 0x10FFFF are converted + to the replacement character; otherwise (when the flag is set to strict) + they constitute an error. + + Output parameters: + The value "sourceIllegal" is returned from some routines if the input + sequence is malformed. When "sourceIllegal" is returned, the source + value will point to the illegal value that caused the problem. E.g., + in UTF-8 when a sequence is malformed, it points to the start of the + malformed sequence. + + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Fixes & updates, Sept 2001. + +------------------------------------------------------------------------ */ + +/* --------------------------------------------------------------------- + The following 4 definitions are compiler-specific. + The C standard does not guarantee that wchar_t has at least + 16 bits, so wchar_t is no less portable than unsigned short! + All should be unsigned values to avoid sign extension during + bit mask & shift operations. +------------------------------------------------------------------------ */ + +typedef unsigned int UTF32; /* at least 32 bits */ +typedef unsigned short UTF16; /* at least 16 bits */ +typedef unsigned char UTF8; /* typically 8 bits */ +typedef unsigned char Boolean; /* 0 or 1 */ + +/* Some fundamental constants */ +#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD +#define UNI_MAX_BMP (UTF32)0x0000FFFF +#define UNI_MAX_UTF16 (UTF32)0x0010FFFF +#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF +#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF + +typedef enum { + conversionOK, /* conversion successful */ + sourceExhausted, /* partial character in source, but hit end */ + targetExhausted, /* insuff. room in target for conversion */ + sourceIllegal /* source sequence is illegal/malformed */ +} ConversionResult; + +typedef enum { + strictConversion = 0, + lenientConversion +} ConversionFlags; + +/* This is for C++ and does no harm in C */ +#ifdef __cplusplus +extern "C" { +#endif + +ConversionResult ConvertUTF8toUTF16 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF16toUTF8 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF8toUTF32 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF32toUTF8 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF16toUTF32 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF32toUTF16 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); + +Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd); + +#ifdef __cplusplus +} +#endif + +/* --------------------------------------------------------------------- */ diff --git a/corelib/src/DBDriver.cpp b/corelib/src/DBDriver.cpp new file mode 100644 index 0000000000..5c92f68b62 --- /dev/null +++ b/corelib/src/DBDriver.cpp @@ -0,0 +1,774 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "rtabmap/core/DBDriver.h" + +#include "Signature.h" +#include "VWDictionary.h" +#include "utilite/UConversion.h" +#include "utilite/UMath.h" +#include "utilite/ULogger.h" +#include "utilite/UTimer.h" +#include "utilite/UStl.h" + +namespace rtabmap { + +DBDriver::DBDriver(const ParametersMap & parameters) : + _minSignaturesToSave(Parameters::defaultDbMinSignaturesToSave()), + _minWordsToSave(Parameters::defaultDbMinWordsToSave()), + _asyncWaiting(true), + _emptyTrashesTime(0) +{ + this->parseParameters(parameters); +} + +DBDriver::~DBDriver() +{ + this->kill(); + this->emptyTrashes(); +} + +void DBDriver::parseParameters(const ParametersMap & parameters) +{ + ParametersMap::const_iterator iter; + if((iter=parameters.find(Parameters::kDbMinSignaturesToSave())) != parameters.end()) + { + _minSignaturesToSave = std::atoi((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kDbMinWordsToSave())) != parameters.end()) + { + _minWordsToSave = std::atoi((*iter).second.c_str()); + } +} + +void DBDriver::closeConnection() +{ + this->kill(); + this->emptyTrashes(); + _dbSafeAccessMutex.lock(); + this->disconnectDatabaseQuery(); + _dbSafeAccessMutex.unlock(); +} + +bool DBDriver::openConnection(const std::string & url) +{ + _url = url; + _dbSafeAccessMutex.lock(); + if(this->connectDatabaseQuery(url)) + { + this->start(); + _dbSafeAccessMutex.unlock(); + return true; + } + _dbSafeAccessMutex.unlock(); + return false; +} + +bool DBDriver::isConnected() const +{ + bool r; + _dbSafeAccessMutex.lock(); + r = isConnectedQuery(); + _dbSafeAccessMutex.unlock(); + return r; +} + +// In bytes +long DBDriver::getMemoryUsed() const +{ + long bytes; + _dbSafeAccessMutex.lock(); + bytes = getMemoryUsedQuery(); + _dbSafeAccessMutex.unlock(); + return bytes; +} + +void DBDriver::mainLoop() +{ + UDEBUG(""); + this->emptyTrashes(); + this->kill(); // Do it only once + UDEBUG(""); +} + +void DBDriver::killCleanup() +{ + UDEBUG(""); +} + +void DBDriver::beginTransaction() const +{ + _transactionMutex.lock(); + ULOGGER_DEBUG(""); + this->executeNoResultQuery("BEGIN TRANSACTION;"); +} + +void DBDriver::commit() const +{ + ULOGGER_DEBUG(""); + this->executeNoResultQuery("COMMIT;"); + _transactionMutex.unlock(); +} + +bool DBDriver::executeNoResult(const std::string & sql) const +{ + bool r; + _dbSafeAccessMutex.lock(); + r = this->executeNoResultQuery(sql); + _dbSafeAccessMutex.unlock(); + return r; +} + +void DBDriver::emptyTrashes(bool async) +{ + ULOGGER_DEBUG(""); + if(async) + { + ULOGGER_DEBUG("Async emptying, start the trash thread"); + this->start(); + return; + } + + UTimer totalTime; + totalTime.start(); + + std::vector signatures; + std::map visualWords; + _trashesMutex.lock(); + { + signatures = uValues(_trashSignatures); + visualWords = _trashVisualWords; + _trashSignatures.clear(); + _trashVisualWords.clear(); + + _asyncWaiting = true; + + _dbSafeAccessMutex.lock(); + } + _trashesMutex.unlock(); + + if(signatures.size() || visualWords.size()) + { + ULOGGER_DEBUG("trashSignatures size = %d, trashVisualWords size = %d", signatures.size(), visualWords.size()); + this->beginTransaction(); + UTimer timer; + timer.start(); + if(signatures.size()) + { + if(this->isConnected()) + { + //Only one query to the database + this->saveOrUpdate(signatures); + } + + for(std::vector::iterator iter=signatures.begin(); iter!=signatures.end(); ++iter) + { + delete *iter; + } + signatures.clear(); + } + ULOGGER_DEBUG("Time emptying memory signatures trash = %f...", timer.ticks()); + if(visualWords.size()) + { + if(this->isConnected()) + { + //Only one query to the database + this->saveQuery(uValues(visualWords)); + } + + for(std::map::iterator iter=visualWords.begin(); iter!=visualWords.end(); ++iter) + { + delete (*iter).second; + } + visualWords.clear(); + } + ULOGGER_DEBUG("Time emptying memory visualWords trash = %f...", timer.ticks()); + this->commit(); + } + + _emptyTrashesTime = totalTime.ticks(); + ULOGGER_DEBUG("Total time emptying trashes = %fs...", _emptyTrashesTime); + + _dbSafeAccessMutex.unlock(); +} + +void DBDriver::asyncSave(Signature * s) +{ + _trashesMutex.lock(); + { + _trashSignatures.insert(std::pair(s->id(), s)); + if(_trashSignatures.size() > _minSignaturesToSave && this->isRunning() && _asyncWaiting) + { + ULOGGER_DEBUG("(Sign) Releasing addSem..."); + _asyncWaiting = false; + this->start(); + } + } + _trashesMutex.unlock(); +} + +void DBDriver::asyncSave(VisualWord * vw) +{ + _trashesMutex.lock(); + { + _trashVisualWords.insert(std::pair(vw->id(), vw)); + if(_trashVisualWords.size() > _minWordsToSave && this->isRunning() && _asyncWaiting) + { + ULOGGER_DEBUG("(Word) Releasing addSem..."); + _asyncWaiting = false; + this->start(); + } + } + _trashesMutex.unlock(); +} + +bool DBDriver::getSignature(int signatureId, Signature ** s) +{ + *s = 0; + _trashesMutex.lock(); + { + _dbSafeAccessMutex.lock(); + _dbSafeAccessMutex.unlock(); + for(std::map::iterator i=_trashSignatures.begin(); i!=_trashSignatures.end();) + { + if(i->first == signatureId) + { + *s = i->second; + _trashSignatures.erase(i++); + break; + } + else + { + ++i; + } + } + } + _trashesMutex.unlock(); + + if(*s == 0) + { + bool r; + _dbSafeAccessMutex.lock(); + r = this->loadQuery(signatureId, s); + _dbSafeAccessMutex.unlock(); + return r; + } + + return true; +} + +bool DBDriver::getVisualWord(int wordId, VisualWord ** vw) +{ + *vw = 0; + _trashesMutex.lock(); + { + _dbSafeAccessMutex.lock(); + _dbSafeAccessMutex.unlock(); + for(std::map::iterator i=_trashVisualWords.begin(); i!=_trashVisualWords.end(); ++i) + { + if((*i).first == wordId) + { + *vw = (*i).second; + _trashVisualWords.erase(i); + break; + } + } + } + _trashesMutex.unlock(); + + if(*vw == 0) + { + bool r; + _dbSafeAccessMutex.lock(); + r = this->loadQuery(wordId, vw); + _dbSafeAccessMutex.unlock(); + return r; + } + + return true; +} + +//Automatically begin and commit a transaction +bool DBDriver::saveOrUpdate(const std::vector & signatures) const +{ + ULOGGER_DEBUG(""); + std::list toSaveK; + std::list toUpdate; + if(this->isConnected() && signatures.size()) + { + for(std::vector::const_iterator i=signatures.begin(); i!=signatures.end();++i) + { + if((*i)->isSaved()) + { + toUpdate.push_back(*i); + } + else if((*i)->signatureType().compare("KeypointSignature") == 0) + { + toSaveK.push_back((KeypointSignature *)(*i)); + } + else + { + ULOGGER_ERROR("Unknown signature type ?!?"); + } + } + + if(toUpdate.size()) + { + this->updateQuery(toUpdate); + } + if(toSaveK.size()) + { + this->saveQuery(toSaveK); + } + } + return false; +} + +bool DBDriver::load(VWDictionary * dictionary) const +{ + bool r; + _dbSafeAccessMutex.lock(); + r = this->loadQuery(dictionary); + _dbSafeAccessMutex.unlock(); + return r; +} + +bool DBDriver::loadLastSignatures(std::list & signatures) const +{ + bool r; + _dbSafeAccessMutex.lock(); + r = this->loadLastSignaturesQuery(signatures); + _dbSafeAccessMutex.unlock(); + return r; +} + +bool DBDriver::loadKeypointSignatures(const std::list & signIds, std::list & signatures, bool onlyParents) +{ + UDEBUG(""); + // look up in the trash before the database + std::list ids = signIds; + std::list::iterator sIter; + bool valueFound = false; + _trashesMutex.lock(); + { + _dbSafeAccessMutex.lock(); + _dbSafeAccessMutex.unlock(); + for(std::list::iterator iter = ids.begin(); iter != ids.end();) + { + valueFound = false; + for(std::map::iterator sIter = _trashSignatures.begin(); sIter!=_trashSignatures.end();) + { + if(sIter->first == *iter) + { + if((onlyParents && sIter->second->getLoopClosureId() == 0) || !onlyParents) + { + signatures.push_back(sIter->second); + _trashSignatures.erase(sIter++); + } + else + { + ++sIter; + } + valueFound = true; + break; + } + else + { + ++sIter; + } + } + if(valueFound) + { + iter = ids.erase(iter); + } + else + { + ++iter; + } + } + } + _trashesMutex.unlock(); + UDEBUG(""); + if(ids.size()) + { + bool r; + _dbSafeAccessMutex.lock(); + r = this->loadKeypointSignaturesQuery(ids, signatures, onlyParents); + _dbSafeAccessMutex.unlock(); + return r; + } + else if(signatures.size()) + { + return true; + } + return false; +} + +bool DBDriver::loadWords(const std::list & wordIds, std::list & vws) +{ + if(!wordIds.size()) + { + return false; + } + // look up in the trash before the database + std::list ids = wordIds; + std::map::iterator wIter; + _trashesMutex.lock(); + { + _dbSafeAccessMutex.lock(); + _dbSafeAccessMutex.unlock(); + for(std::list::iterator iter = ids.begin(); iter != ids.end();) + { + wIter = _trashVisualWords.find(*iter); + if(wIter != _trashVisualWords.end()) + { + //UDEBUG("put back word %d from trash", *iter); + vws.push_back(wIter->second); + _trashVisualWords.erase(wIter); + iter = ids.erase(iter); + } + else + { + ++iter; + } + } + } + _trashesMutex.unlock(); + if(ids.size()) + { + bool r; + _dbSafeAccessMutex.lock(); + r = this->loadWordsQuery(ids, vws); + _dbSafeAccessMutex.unlock(); + return r; + } + else if(vws.size()) + { + return true; + } + return false; +} + +// +bool DBDriver::changeWordsRef(const std::map & refsToChange) +{ + //Change references in the trash + KeypointSignature * s = 0; + UTimer timer; + _trashesMutex.lock(); + { + _dbSafeAccessMutex.lock(); + _dbSafeAccessMutex.unlock(); + timer.start(); + for(std::map::iterator iter = _trashSignatures.begin(); iter!=_trashSignatures.end(); ++iter) + { + s = dynamic_cast(iter->second); + if(s) + { + for(std::map::const_iterator jter = refsToChange.begin(); jter!=refsToChange.end(); ++jter) + { + s->changeWordsRef((*jter).first, (*jter).second); + } + } + } + ULOGGER_DEBUG("Trash changing words references time=%fs", timer.ticks()); + } + _trashesMutex.unlock(); + + bool r; + _dbSafeAccessMutex.lock(); + r = this->changeWordsRefQuery(refsToChange); + _dbSafeAccessMutex.unlock(); + return r; +} + +bool DBDriver::deleteWords(const std::vector & ids) +{ + //Delete words in the trash + std::map::iterator iter; + _trashesMutex.lock(); + { + _dbSafeAccessMutex.lock(); + _dbSafeAccessMutex.unlock(); + for(unsigned int i=0; ideleteWordsQuery(ids); + _dbSafeAccessMutex.unlock(); + return r; +} + +bool DBDriver::deleteAllVisualWords() const +{ + ULOGGER_DEBUG(""); + if(this->isConnected()) + { + std::string query; + query += "DELETE FROM VisualWord;"; + + _dbSafeAccessMutex.lock(); + bool r = this->executeNoResultQuery(query); + _dbSafeAccessMutex.unlock(); + return r; + } + return false; +} + +bool DBDriver::deleteAllObsoleteSSVWLinks() const +{ + ULOGGER_DEBUG(""); + if(this->isConnected()) + { + std::string query; + query += "DELETE FROM Map_SS_VW WHERE NOT EXISTS (SELECT id FROM VisualWord WHERE id = Map_SS_VW.visualWordId);"; + + _dbSafeAccessMutex.lock(); + bool r = this->executeNoResultQuery(query); + _dbSafeAccessMutex.unlock(); + return r; + } + return false; +} + +bool DBDriver::deleteUnreferencedWords() const +{ + ULOGGER_DEBUG(""); + if(this->isConnected()) + { + std::string query = "DELETE FROM visualword WHERE id NOT IN (SELECT visualWordid FROM map_ss_vw);"; + _dbSafeAccessMutex.lock(); + bool r = this->executeNoResultQuery(query); + _dbSafeAccessMutex.unlock(); + return r; + } + return false; +} + +bool DBDriver::addNeighbor(int id, int neighbor, const std::list > & actuatorStates) +{ + bool r = false; + Signature * s = 0; + _trashesMutex.lock(); + s = uValue(_trashSignatures, id, s); + if(s) + { + s->addNeighbor(neighbor, actuatorStates); + r = true; + } + _trashesMutex.unlock(); + + if(!r) + { + _dbSafeAccessMutex.lock(); + r = this->addNeighborQuery(id, neighbor, actuatorStates); + _dbSafeAccessMutex.unlock(); + } + + return r; +} + +bool DBDriver::removeNeighbor(int id, int neighbor) +{ + bool r = false; + Signature * s = 0; + _trashesMutex.lock(); + s = uValue(_trashSignatures, id, s); + if(s) + { + s->removeNeighbor(neighbor); + r = true; + } + _trashesMutex.unlock(); + + if(!r) + { + r = executeNoResult("DELETE FROM Neighbor WHERE sid=" + uNumber2str(id) + " AND nid=" + uNumber2str(neighbor)); + } + + return r; +} + +//TODO Check also in the trash ? +bool DBDriver::getImage(int id, IplImage ** img) const +{ + CvMat * compressed = 0; + _dbSafeAccessMutex.lock(); + bool result = this->getImageCompressedQuery(id, &compressed); + if(compressed) + { + (*img) = cvDecodeImage(compressed, CV_LOAD_IMAGE_ANYCOLOR); + cvReleaseMat(&compressed); + } + _dbSafeAccessMutex.unlock(); + return result; +} + +//TODO Check also in the trash ? +bool DBDriver::getNeighborIds(int signatureId, std::set & neighbors) const +{ + bool r; + _dbSafeAccessMutex.lock(); + r = this->getNeighborIdsQuery(signatureId, neighbors); + _dbSafeAccessMutex.unlock(); + return r; +} + +//TODO Check also in the trash ? +bool DBDriver::loadNeighbors(int signatureId, std::map > > & neighbors) const +{ + bool r; + _dbSafeAccessMutex.lock(); + r = this->loadNeighborsQuery(signatureId, neighbors); + _dbSafeAccessMutex.unlock(); + return r; +} + +//TODO Check also in the trash ? +bool DBDriver::getWeight(int signatureId, int & weight) const +{ + bool r; + _dbSafeAccessMutex.lock(); + r = this->getWeightQuery(signatureId, weight); + _dbSafeAccessMutex.unlock(); + return r; +} + +//TODO Check also in the trash ? +bool DBDriver::getLoopClosureId(int signatureId, int & loopId) const +{ + bool r; + _dbSafeAccessMutex.lock(); + r = this->getLoopClosureIdQuery(signatureId, loopId); + _dbSafeAccessMutex.unlock(); + return r; +} + +//TODO Check also in the trash ? +bool DBDriver::getImageCompressed(int id, CvMat ** compressed) const +{ + bool r; + _dbSafeAccessMutex.lock(); + r = this->getImageCompressedQuery(id, compressed); + _dbSafeAccessMutex.unlock(); + return r; +} + +//TODO Check also in the trash ? +bool DBDriver::getAllSignatureIds(std::set & ids) const +{ + bool r; + _dbSafeAccessMutex.lock(); + r = this->getAllSignatureIdsQuery(ids); + _dbSafeAccessMutex.unlock(); + return r; +} + +//TODO Check also in the trash ? +bool DBDriver::getLastSignatureId(int & id) const +{ + bool r; + _dbSafeAccessMutex.lock(); + r = this->getLastSignatureIdQuery(id); + _dbSafeAccessMutex.unlock(); + return r; +} + +//TODO Check also in the trash ? +bool DBDriver::getLastVisualWordId(int & id) const +{ + bool r; + _dbSafeAccessMutex.lock(); + r = this->getLastVisualWordIdQuery(id); + _dbSafeAccessMutex.unlock(); + return r; +} + +//TODO Check also in the trash ? +bool DBDriver::getSurfNi(int signatureId, int & ni) const +{ + bool r; + _dbSafeAccessMutex.lock(); + r = this->getSurfNiQuery(signatureId, ni); + _dbSafeAccessMutex.unlock(); + return r; +} + +//TODO Check also in the trash ? +bool DBDriver::getChildrenIds(int signatureId, std::list & ids) const +{ + bool r; + _dbSafeAccessMutex.lock(); + r = this->getChildrenIdsQuery(signatureId, ids); + _dbSafeAccessMutex.unlock(); + return r; +} + +bool DBDriver::getHighestWeightedSignatures(unsigned int count, std::multimap & ids) const +{ + bool r; + _dbSafeAccessMutex.lock(); + r = this->getHighestWeightedSignaturesQuery(count, ids); + _dbSafeAccessMutex.unlock(); + return r; +} + +bool DBDriver::addStatisticsAfterRun(int stMemSize, int lastSignAdded, int processMemUsed, int databaseMemUsed) const +{ + ULOGGER_DEBUG(""); + if(this->isConnected()) + { + std::stringstream query; + query << "INSERT INTO StatisticsAfterRun(stMemSize,lastSignAdded,processMemUsed,databaseMemUsed) values(" + << stMemSize << "," + << lastSignAdded << "," + << processMemUsed << "," + << databaseMemUsed << ");"; + + bool r = this->executeNoResultQuery(query.str()); + return r; + } + return false; +} + +bool DBDriver::addStatisticsAfterRunSurf(int dictionarySize) const +{ + ULOGGER_DEBUG(""); + if(this->isConnected()) + { + std::stringstream query; + query << "INSERT INTO StatisticsAfterRunSurf(dictionarySize) values(" << dictionarySize << ");"; + + bool r = this->executeNoResultQuery(query.str()); + return r; + } + return false; +} + +} // namespace rtabmap diff --git a/corelib/src/DBDriverFactory.cpp b/corelib/src/DBDriverFactory.cpp new file mode 100644 index 0000000000..6b96be46b8 --- /dev/null +++ b/corelib/src/DBDriverFactory.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "rtabmap/core/DBDriverFactory.h" +#include "DBDriverSqlite3.h" +#include "utilite/ULogger.h" + +namespace rtabmap { + +DBDriver * DBDriverFactory::createDBDriver(const std::string & dbDriverName, const ParametersMap & parameters) +{ + // TODO Do it with dynamic link libraries... + // Find the driver... + // Link dynamically to the driver... + + DBDriver * driver = 0; + + // Static link + if(dbDriverName.compare("sqlite3") == 0) + { + driver = new DBDriverSqlite3(parameters); + } + else if(dbDriverName.compare("mysql") == 0) + { + // TODO mysql driver + ULOGGER_ERROR("mysql driver is not implemented!"); + } + else if(dbDriverName.compare("postgresql") == 0) + { + // TODO postgresql driver + ULOGGER_ERROR("postgresql driver is not implemented!"); + } + else if(dbDriverName.compare("oracle") == 0) + { + // TODO oracle driver + ULOGGER_ERROR("oracle driver is not implemented!"); + } + else + { + ULOGGER_ERROR("Unknown driver \"%s\"", dbDriverName.c_str()); + } + + return driver; +} + +DBDriverFactory::DBDriverFactory() { + +} + +DBDriverFactory::~DBDriverFactory() { +} + +} + diff --git a/corelib/src/DBDriverSqlite3.cpp b/corelib/src/DBDriverSqlite3.cpp new file mode 100644 index 0000000000..ada94859d6 --- /dev/null +++ b/corelib/src/DBDriverSqlite3.cpp @@ -0,0 +1,3082 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "DBDriverSqlite3.h" + +#include "Signature.h" +#include "VisualWord.h" +#include "VWDictionary.h" +#include "DatabaseSchema_sql.h" +#include + +#include "utilite/UtiLite.h" + +namespace rtabmap { + +DBDriverSqlite3::DBDriverSqlite3(const ParametersMap & parameters) : + DBDriver(parameters), + _ppDb(0), + _dbInMemory(Parameters::defaultDbSqlite3InMemory()), + _cacheSize(Parameters::defaultDbSqlite3CacheSize()), + _journalMode(Parameters::defaultDbSqlite3JournalMode()) +{ + ULOGGER_DEBUG("treadSafe=%d", sqlite3_threadsafe()); + this->parseParameters(parameters); +} + +DBDriverSqlite3::~DBDriverSqlite3() +{ + this->closeConnection(); +} + +void DBDriverSqlite3::parseParameters(const ParametersMap & parameters) +{ + ParametersMap::const_iterator iter; + if((iter=parameters.find(Parameters::kDbSqlite3CacheSize())) != parameters.end()) + { + this->setCacheSize(std::atoi((*iter).second.c_str())); + } + if((iter=parameters.find(Parameters::kDbSqlite3JournalMode())) != parameters.end()) + { + this->setJournalMode(std::atoi((*iter).second.c_str())); + } + if((iter=parameters.find(Parameters::kDbSqlite3InMemory())) != parameters.end()) + { + this->setDbInMemory(uStr2Bool((*iter).second.c_str())); + } + DBDriver::parseParameters(parameters); +} + +void DBDriverSqlite3::setCacheSize(unsigned int cacheSize) +{ + if(this->isConnected()) + { + _cacheSize = cacheSize; + std::string query = "PRAGMA cache_size = "; + query += uNumber2str(_cacheSize) + ";"; + this->executeNoResultQuery(query.c_str()); + } +} + +void DBDriverSqlite3::setJournalMode(int journalMode) +{ + if(journalMode >= 0 && journalMode < 5) + { + _journalMode = journalMode; + if(this->isConnected()) + { + switch(_journalMode) + { + case 4: + this->executeNoResultQuery("PRAGMA journal_mode = OFF;"); + break; + case 3: + this->executeNoResultQuery("PRAGMA journal_mode = MEMORY;"); + break; + case 2: + this->executeNoResultQuery("PRAGMA journal_mode = PERSIST;"); + break; + case 1: + this->executeNoResultQuery("PRAGMA journal_mode = TRUNCATE;"); + break; + case 0: + default: + this->executeNoResultQuery("PRAGMA journal_mode = DELETE;"); + break; + } + } + } + else + { + ULOGGER_ERROR("Wrong journal mode (%d)", journalMode); + } +} + +void DBDriverSqlite3::setDbInMemory(bool dbInMemory) +{ + if(dbInMemory != _dbInMemory) + { + if(this->isConnected()) + { + // Hard reset... + this->kill(); + this->emptyTrashes(); + this->closeConnection(); + _dbInMemory = dbInMemory; + this->openConnection(this->getUrl()); + this->start(); + } + else + { + _dbInMemory = dbInMemory; + } + } +} + +/* +** This function is used to load the contents of a database file on disk +** into the "main" database of open database connection pInMemory, or +** to save the current contents of the database opened by pInMemory into +** a database file on disk. pInMemory is probably an in-memory database, +** but this function will also work fine if it is not. +** +** Parameter zFilename points to a nul-terminated string containing the +** name of the database file on disk to load from or save to. If parameter +** isSave is non-zero, then the contents of the file zFilename are +** overwritten with the contents of the database opened by pInMemory. If +** parameter isSave is zero, then the contents of the database opened by +** pInMemory are replaced by data loaded from the file zFilename. +** +** If the operation is successful, SQLITE_OK is returned. Otherwise, if +** an error occurs, an SQLite error code is returned. +*/ +int DBDriverSqlite3::loadOrSaveDb(sqlite3 *pInMemory, const std::string & fileName, int isSave) const +{ + int rc; /* Function return code */ + sqlite3 *pFile = 0; /* Database connection opened on zFilename */ + sqlite3_backup *pBackup = 0; /* Backup object used to copy data */ + sqlite3 *pTo = 0; /* Database to copy to (pFile or pInMemory) */ + sqlite3 *pFrom = 0; /* Database to copy from (pFile or pInMemory) */ + + /* Open the database file identified by zFilename. Exit early if this fails + ** for any reason. */ + rc = sqlite3_open(fileName.c_str(), &pFile); + if( rc==SQLITE_OK ){ + + /* If this is a 'load' operation (isSave==0), then data is copied + ** from the database file just opened to database pInMemory. + ** Otherwise, if this is a 'save' operation (isSave==1), then data + ** is copied from pInMemory to pFile. Set the variables pFrom and + ** pTo accordingly. */ + pFrom = (isSave ? pInMemory : pFile); + pTo = (isSave ? pFile : pInMemory); + + /* Set up the backup procedure to copy from the "main" database of + ** connection pFile to the main database of connection pInMemory. + ** If something goes wrong, pBackup will be set to NULL and an error + ** code and message left in connection pTo. + ** + ** If the backup object is successfully created, call backup_step() + ** to copy data from pFile to pInMemory. Then call backup_finish() + ** to release resources associated with the pBackup object. If an + ** error occurred, then an error code and message will be left in + ** connection pTo. If no error occurred, then the error code belonging + ** to pTo is set to SQLITE_OK. + */ + pBackup = sqlite3_backup_init(pTo, "main", pFrom, "main"); + if( pBackup ){ + (void)sqlite3_backup_step(pBackup, -1); + (void)sqlite3_backup_finish(pBackup); + } + rc = sqlite3_errcode(pTo); + } + + /* Close the database connection opened on database file zFilename + ** and return the result of this function. */ + (void)sqlite3_close(pFile); + return rc; +} + + +bool DBDriverSqlite3::connectDatabaseQuery(const std::string & url) +{ + // Open a database connection + _ppDb = 0; + int rc = SQLITE_OK; + bool dbFileExist = UFile::exists(url.c_str()); + + if(_dbInMemory) + { + ULOGGER_INFO("Using database \"%s\" in the memory.", url.c_str()); + rc = sqlite3_open_v2(":memory:", &_ppDb, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0); + } + else + { + ULOGGER_INFO("Using database \"%s\" from the hard drive.", url.c_str()); + rc = sqlite3_open_v2(url.c_str(), &_ppDb, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0); + } + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error : %s", sqlite3_errmsg(_ppDb)); + _ppDb = 0; + return false; + } + + if(_dbInMemory && dbFileExist) + { + UTimer timer; + timer.start(); + ULOGGER_DEBUG("Loading DB ..."); + rc = loadOrSaveDb(_ppDb, url, 0); // Load memory from file + ULOGGER_INFO("Loading DB time = %fs, (%s)", timer.ticks(), url.c_str()); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2 : %s", sqlite3_errmsg(_ppDb)); + sqlite3_close(_ppDb); + _ppDb = 0; + return false; + } + } + + if(!dbFileExist) + { + ULOGGER_INFO("Database \"%s\" doesn't exist, creating a new one...", url.c_str()); + // Create the database + std::string schema = DATABASESCHEMA_SQL; + schema = uHex2str(schema); + if(this->executeNoResultQuery(schema.c_str())) + { + ULOGGER_DEBUG("Database schema created."); + } + else + { + ULOGGER_ERROR("Database creation failed!"); + return false; + } + } + + //Set database optimizations + this->setCacheSize(_cacheSize); // this will call the SQL + this->setJournalMode(_journalMode); // this will call the SQL + + return true; +} +void DBDriverSqlite3::disconnectDatabaseQuery() +{ + if(_ppDb) + { + int rc = SQLITE_OK; + // make sure that all statements are finalized + sqlite3_stmt * pStmt; + while( (pStmt = sqlite3_next_stmt(_ppDb, 0))!=0 ) + { + sqlite3_finalize(pStmt); + } + + if(_dbInMemory) + { + UTimer timer; + timer.start(); + ULOGGER_DEBUG("Saving DB ..."); + rc = loadOrSaveDb(_ppDb, this->getUrl(), 1); // Save memory to file + ULOGGER_DEBUG("Saving DB time = %fs", timer.ticks()); + } + + // Then close (delete) the database connection + sqlite3_close(_ppDb); + _ppDb = 0; + } +} + +bool DBDriverSqlite3::isConnectedQuery() const +{ + return _ppDb; +} + +// In bytes +long DBDriverSqlite3::getMemoryUsedQuery() const +{ + if(_dbInMemory) + { + return sqlite3_memory_used(); + } + else + { + return UFile::length(this->getUrl()); + } +} + +bool DBDriverSqlite3::getImageCompressedQuery(int id, CvMat ** compressed) const +{ + if(_ppDb) + { + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + + query << "SELECT image " + << "FROM Signature " + << "WHERE id = " << id <<";"; + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + const void * data = 0; + int dataSize = 0; + + // Process the result if one + rc = sqlite3_step(ppStmt); + if(rc == SQLITE_ROW) + { + data = sqlite3_column_blob(ppStmt, 0); + dataSize = sqlite3_column_bytes(ppStmt, 0); + //Create the image + if(dataSize && data) + { + *compressed = cvCreateMat(1, dataSize, CV_8UC1); + memcpy(&((*compressed)->data.ptr[0]), (const char *)data, dataSize); + } + + rc = sqlite3_step(ppStmt); + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("Supposed to received only 1 result... "); + rc = sqlite3_finalize(ppStmt); + return false; + } + } + else + { + ULOGGER_WARN("No result !?! from the DB"); + } + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + ULOGGER_DEBUG("Time=%fs", timer.ticks()); + return true; + } + return false; +} + +bool DBDriverSqlite3::getAllSignatureIdsQuery(std::set & ids) const +{ + if(_ppDb) + { + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + + query << "SELECT id " + << "FROM Signature " + << "WHERE loopClosureId == 0 " + << "ORDER BY id"; + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + + // Process the result if one + rc = sqlite3_step(ppStmt); + while(rc == SQLITE_ROW) + { + ids.insert(ids.end(), sqlite3_column_int(ppStmt, 0)); // Signature Id + rc = sqlite3_step(ppStmt); + } + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + ULOGGER_DEBUG("Time=%f", timer.ticks()); + return true; + } + return false; +} + +bool DBDriverSqlite3::getLastSignatureIdQuery(int & id) const +{ + if(_ppDb) + { + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + + //query.append("BEGIN TRANSACTION;"); + + // Create a new entry in table KeypointSignature + query << "SELECT max(id) " + << "FROM Signature;"; + + //query.append("COMMIT;"); + + //ULOGGER_DEBUG("DBDriverSqlite3::getLastSignatureId() Execute query : %s", query.toStdString().c_str()); + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + + // Process the result if one + rc = sqlite3_step(ppStmt); + if(rc == SQLITE_ROW) + { + id = sqlite3_column_int(ppStmt, 0); // Signature Id + rc = sqlite3_step(ppStmt); + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("Supposed to received only 1 result... "); + rc = sqlite3_finalize(ppStmt); + return false; + } + } + else + { + ULOGGER_ERROR("No result !?! from the DB"); + } + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + ULOGGER_DEBUG("Time=%fs", timer.ticks()); + return true; + } + return false; +} + +bool DBDriverSqlite3::getLastVisualWordIdQuery(int & id) const +{ + if(_ppDb && id > 0) + { + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + + //query.append("BEGIN TRANSACTION;"); + + // Create a new entry in table KeypointSignature + query << "SELECT max(id) " + << "FROM VisualWord;"; + + //query.append("COMMIT;"); + + //ULOGGER_DEBUG("DBDriverSqlite3::getLastSignatureId() Execute query : %s", query.toStdString().c_str()); + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + + // Process the result if one + rc = sqlite3_step(ppStmt); + if(rc == SQLITE_ROW) + { + id = sqlite3_column_int(ppStmt, 0); // VisualWord Id + rc = sqlite3_step(ppStmt); + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("Supposed to received only 1 result... "); + rc = sqlite3_finalize(ppStmt); + return false; + } + } + else + { + ULOGGER_ERROR("No result !?! from the DB"); + } + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + ULOGGER_DEBUG("Time=%fs", timer.ticks()); + return true; + } + return false; +} + +bool DBDriverSqlite3::getSurfNiQuery(int signatureId, int & ni) const +{ + ni = 0; + if(_ppDb) + { + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + + // Create a new entry in table KeypointSignature + query << "SELECT count(visualWordId) " + << "FROM Map_SS_VW " + << "WHERE signatureId=" << signatureId << ";"; + + //query.append("COMMIT;"); + + //ULOGGER_DEBUG("DBDriverSqlite3::getSurfNi() Execute query : %s", query.toStdString().c_str()); + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + + // Process the result if one + rc = sqlite3_step(ppStmt); + if(rc == SQLITE_ROW) + { + ni = sqlite3_column_int(ppStmt, 0); + rc = sqlite3_step(ppStmt); + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("Supposed to received only 1 result... signature=%d", signatureId); + rc = sqlite3_finalize(ppStmt); + return false; + } + } + else + { + ULOGGER_ERROR("No result !?! from the DB, signature=%d",signatureId); + } + + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + ULOGGER_DEBUG("Time=%fs", timer.ticks()); + return true; + } + return false; +} + +bool DBDriverSqlite3::getChildrenIdsQuery(int signatureId, std::list & ids) const +{ + ids.clear(); + if(_ppDb) + { + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + + query << "SELECT id " + << "FROM Signature " + << "WHERE loopClosureId = " << signatureId << ";"; + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + + // Process the result if one + rc = sqlite3_step(ppStmt); + while(rc == SQLITE_ROW) + { + ids.push_back(sqlite3_column_int(ppStmt, 0)); // Signature Id + rc = sqlite3_step(ppStmt); + } + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + ULOGGER_DEBUG("Time=%f", timer.ticks()); + return true; + } + return false; +} + +//only returns neighbors with no parentId and no loopClosureId +bool DBDriverSqlite3::getNeighborIdsQuery(int signatureId, std::set & neighbors) const +{ + neighbors.clear(); + if(_ppDb) + { + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + + query << "SELECT nid FROM Neighbor LEFT JOIN Signature ON Signature.id = nid " + << "WHERE sid = " << signatureId << " AND " + << "(Signature.loopClosureId = 0 OR Signature.loopClosureId is null) "; + // << "ORDER BY Signature.weight DESC, Signature.id DESC;"; + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + + // Process the result if one + rc = sqlite3_step(ppStmt); + while(rc == SQLITE_ROW) + { + neighbors.insert(neighbors.end(), (sqlite3_column_int(ppStmt, 0))); // nid + rc = sqlite3_step(ppStmt); + } + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + //for(std::set::iterator i=neighbors.begin(); i!=neighbors.end(); ++i) + //{ + // ULOGGER_DEBUG("signatureId=%d, nId = %d", signatureId, *i); + //} + return true; + } + return false; +} + +bool DBDriverSqlite3::getWeightQuery(int signatureId, int & weight) const +{ + weight = 0; + if(_ppDb) + { + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + + query << "SELECT weight FROM signature WHERE id = " + << signatureId + << ";"; + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + + // Process the result if one + rc = sqlite3_step(ppStmt); + if(rc == SQLITE_ROW) + { + weight= sqlite3_column_int(ppStmt, 0); // weight + rc = sqlite3_step(ppStmt); + } + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + return true; + } + return false; +} + +bool DBDriverSqlite3::getLoopClosureIdQuery(int signatureId, int & loopId) const +{ + loopId = 0; + if(_ppDb) + { + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + + query << "SELECT loopClosureId FROM signature WHERE id = " + << signatureId + << ";"; + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + + // Process the result if one + rc = sqlite3_step(ppStmt); + if(rc == SQLITE_ROW) + { + loopId= sqlite3_column_int(ppStmt, 0); // loopClosureId + rc = sqlite3_step(ppStmt); + } + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + return true; + } + return false; +} + +bool DBDriverSqlite3::addNeighborQuery(int id, int neighbor, const std::list > & actuatorStates) const +{ + ULOGGER_DEBUG("id=%d, neighbor=%d, actuatorStates=%d", id, neighbor, actuatorStates.size()); + if(_ppDb) + { + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::string query; + + if(!actuatorStates.size()) + { + query = std::string("INSERT INTO Neighbor(sid, nid) VALUES(?,?);"); + } + else + { + query = std::string("INSERT INTO Neighbor(sid, nid, actionSize, actions) VALUES(?,?,?,?);"); + } + + + rc = sqlite3_prepare_v2(_ppDb, query.c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + rc = sqlite3_bind_int(ppStmt, 1, id); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_int(ppStmt, 2, neighbor); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + if(actuatorStates.size()) + { + unsigned int actionSize = actuatorStates.front().size(); + rc = sqlite3_bind_int(ppStmt, 3, actionSize); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.3: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + //Concatenate actions + std::vector actions; + for(std::list >::const_iterator iter=actuatorStates.begin(); iter!=actuatorStates.end(); ++iter) + { + if(iter->size() != actionSize) + { + UERROR("Actuator states must be all the same size! (actionSize=%d != currentAction%d)", actionSize, iter->size()); + } + actions.insert(actions.end(), iter->begin(), iter->end()); + } + rc = sqlite3_bind_blob(ppStmt, 4, (unsigned char *)actions.data(), actions.size()*sizeof(float), SQLITE_STATIC); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.4: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + } + + //execute query + rc=sqlite3_step(ppStmt); + if (rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 5: %s", sqlite3_errmsg(_ppDb)); + return false; + } + + ULOGGER_DEBUG("Time=%fs", timer.ticks()); + + return true; + } + return false; +} + +//return +bool DBDriverSqlite3::getHighestWeightedSignaturesQuery(unsigned int count, std::multimap & ids) const +{ + if(_ppDb && count) + { + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + + query << "SELECT weight,id " + << "FROM Signature " + << "WHERE loopClosureId = 0 " + << "ORDER BY weight DESC " + << "LIMIT " << count << ";"; + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + + // Process the result if one + rc = sqlite3_step(ppStmt); + int weight = 0; + int id = 0; + while(rc == SQLITE_ROW) + { + weight = sqlite3_column_int(ppStmt, 0); // Weight + id = sqlite3_column_int(ppStmt, 1); // Signature Id + if(ids.size() < count || + (ids.size() && ids.begin()->first < weight)) + { + ids.insert(std::pair(weight, id)); + } + if(ids.size() >= count) + { + ids.erase(ids.begin()); + } + rc = sqlite3_step(ppStmt); + } + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + ULOGGER_DEBUG("Time=%f", timer.ticks()); + return true; + } + return false; +} + +bool DBDriverSqlite3::executeNoResultQuery(const std::string & sql) const +{ + if(_ppDb) + { + UTimer timer; + timer.start(); + int rc; + rc = sqlite3_exec(_ppDb, sql.c_str(), 0, 0, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1 (rc=%d): %s", rc, sqlite3_errmsg(_ppDb)); + ULOGGER_ERROR("The query is: %s", sql.c_str()); + return false; + } + ULOGGER_DEBUG("Time=%fs", timer.ticks()); + return true; + } + return false; +} + +bool DBDriverSqlite3::loadQuery(int signatureId, Signature ** s) const +{ + ULOGGER_DEBUG("Signature"); + *s = 0; + if(_ppDb) + { + std::string type; + int weight = 0; + int loopClosureId = 0; + int imgWidth = 0; + int imgHeight = 0; + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + + // Create a new entry in table KeypointSignature + query << "SELECT type, weight, loopClosureId, imgWidth, imgHeight " + << "FROM Signature " + << "WHERE id=" << signatureId << ";"; + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + + // Process the result if one + rc = sqlite3_step(ppStmt); + if(rc == SQLITE_ROW) + { + type = std::string((const char *)sqlite3_column_text(ppStmt, 0)); // Signature type + weight = sqlite3_column_int(ppStmt, 1); // weight + loopClosureId = sqlite3_column_int(ppStmt, 2); // loopClosureId + imgWidth = sqlite3_column_int(ppStmt, 3); // imgWidth + imgHeight = sqlite3_column_int(ppStmt, 4); // imgHeight + rc = sqlite3_step(ppStmt); + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("Supposed to received only 1 result... signature=%d", signatureId); + rc = sqlite3_finalize(ppStmt); + return false; + } + } + else + { + ULOGGER_ERROR("No result !?! from the DB, signature=%d",signatureId); + } + + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + ULOGGER_DEBUG("Time=%fs", timer.ticks()); + + //get neighbors + NeighborsMap neighbors; + this->loadNeighborsQuery(signatureId, neighbors); + + if(type.compare("surf") == 0) + { + *s = new KeypointSignature(signatureId); + if(*s) + { + (*s)->setWeight(weight); + (*s)->setLoopClosureId(loopClosureId); + (*s)->setWidth(imgWidth); + (*s)->setHeight(imgHeight); + (*s)->addNeighbors(neighbors); + if(this->loadQuery(signatureId, (KeypointSignature*)*s)) + { + (*s)->setSaved(true); + return true; + } + else if(*s) + { + delete *s; + *s = 0; + } + } + } + + return false; // signature not created + } + return false; +} + +bool DBDriverSqlite3::loadQuery(int signatureId, KeypointSignature * ss) const +{ + ULOGGER_DEBUG("KeypointSignature"); + if(_ppDb && ss) + { + std::string type; + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + std::multimap visualWords; + + // Get the map from signature and visual words + query << "SELECT visualWordId, pos_x, pos_y, laplacian, size, dir, hessian " + << "FROM Map_SS_VW " + << "WHERE signatureId=" << signatureId + << " ORDER BY visualWordId;"; // used for fast insertion below + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + // Process the result if one + rc = sqlite3_step(ppStmt); + int visualWordId = 0; + float pos_x = 0; + float pos_y = 0; + int laplacian = 0; + int size = 0; + float dir = 0; + float hessian = 0; + while(rc == SQLITE_ROW) + { + visualWordId = sqlite3_column_int(ppStmt, 0); + pos_x = sqlite3_column_double(ppStmt, 1); + pos_y = sqlite3_column_double(ppStmt, 2); + laplacian = sqlite3_column_int(ppStmt, 3); + size = sqlite3_column_int(ppStmt, 4); + dir = sqlite3_column_double(ppStmt, 5); + hessian = sqlite3_column_double(ppStmt, 6); + visualWords.insert(visualWords.end(), std::pair(visualWordId, cv::KeyPoint(cv::Point2f(pos_x,pos_y),size,dir,hessian, 0, -1))); + rc = sqlite3_step(ppStmt); + } + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + + ss->setWords(visualWords); + + ULOGGER_DEBUG("Time=%fs", timer.ticks()); + return true; + } + return false; +} +/* +bool DBDriverSqlite3::loadKeypointSignaturesQuery(const std::list & ids, std::list & signatures, bool onlyParents) const +{ + ULOGGER_DEBUG("count=%d", ids.size()); + if(_ppDb && ids.size()) + { + std::string type; + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + std::multimap visualWords; + unsigned int loaded = 0; + + // Prepare the query... Get the map from signature and visual words + query << "SELECT id, weight, loopClosureId, imgWidth, imgHeight, visualWordId, pos_x, pos_y, laplacian, size, dir, hessian " + "FROM Map_SS_VW " + "INNER JOIN Signature " + "ON Signature.id = signatureId " + "WHERE type = 'surf' "; + + if(onlyParents) + { + query << "AND loopClosureId = 0 "; + } + query << "AND (signatureId="; + for(std::list::const_iterator iter=ids.begin(); iter!=ids.end();) + { + UDEBUG("Loading %d", *iter); + query << *iter; + if(++iter != ids.end()) + { + query << " or signatureId="; + } + } + query << ") "; + query << "ORDER BY signatureId AND visualWordId"; // Needed for fast insertion below + query << ";"; + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + int id = 0; + int weight = 0; + int loopClosureId = 0; + int imgWidth = 0; + int imgHeight = 0; + int visualWordId = 0; + float pos_x = 0; + float pos_y = 0; + int laplacian = 0; + int size = 0; + float dir = 0; + float hessian = 0; + + int lastId = 0; + // Process the result if one + rc = sqlite3_step(ppStmt); + while(rc == SQLITE_ROW) + { + id = sqlite3_column_int(ppStmt, 0); // Signature Id + if(id!=lastId && lastId > 0) + { + ULOGGER_DEBUG("Creating %d with %d keypoints", lastId, visualWords.size()); + ++loaded; + KeypointSignature * ss = new KeypointSignature(visualWords, lastId); + if(ss) + { + ss->setWeight(weight); + ss->setLoopClosureId(loopClosureId); + ss->setWidth(imgWidth); + ss->setHeight(imgHeight); + ss->setSaved(true); + signatures.push_back(ss); + } + visualWords.clear(); + + weight = sqlite3_column_int(ppStmt, 1); // weight + loopClosureId = sqlite3_column_int(ppStmt, 2); // loopClosureId + imgWidth = sqlite3_column_int(ppStmt, 3); // imgWidth + imgHeight = sqlite3_column_int(ppStmt, 4); // imgheight + } + else if(lastId == 0) + { + weight = sqlite3_column_int(ppStmt, 1); // weight + loopClosureId = sqlite3_column_int(ppStmt, 2); // loopClosureId + imgWidth = sqlite3_column_int(ppStmt, 3); // imgWidth + imgHeight = sqlite3_column_int(ppStmt, 4); // imgheight + } + lastId = id; + visualWordId = sqlite3_column_int(ppStmt, 5); + pos_x = sqlite3_column_double(ppStmt, 6); + pos_y = sqlite3_column_double(ppStmt, 7); + laplacian = sqlite3_column_int(ppStmt, 8); + size = sqlite3_column_int(ppStmt, 9); + dir = sqlite3_column_double(ppStmt, 10); + hessian = sqlite3_column_double(ppStmt, 11); + + visualWords.insert(visualWords.end(), std::pair(visualWordId, cv::KeyPoint(cv::Point2f(pos_x,pos_y),size,dir,hessian, 0, -1))); + rc = sqlite3_step(ppStmt); + } + + // create the last signature + if(lastId) + { + ++loaded; + ULOGGER_DEBUG("Creating %d with %d keypoints", lastId, visualWords.size()); + KeypointSignature * ss = new KeypointSignature(visualWords, lastId); + if(ss) + { + ss->setWeight(weight); + ss->setLoopClosureId(loopClosureId); + ss->setWidth(imgWidth); + ss->setHeight(imgHeight); + ss->setSaved(true); + signatures.push_back(ss); + } + } + + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 2.2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + ULOGGER_DEBUG("Time=%fs", timer.ticks()); + + for(std::list::iterator i=signatures.begin(); i!=signatures.end(); ++i) + { + NeighborsMap neighbors; + this->loadNeighborsQuery((*i)->id(), neighbors); + (*i)->addNeighbors(neighbors); + } + ULOGGER_DEBUG("Time load neighbors=%fs", timer.ticks()); + + if(ids.size() != loaded && !onlyParents) + { + UERROR("Some signatures not found in database"); + } + + return true; + } + return false; +}*/ + +//may be slower than the previous version but don't have a limit of words that can be loaded at the same time +bool DBDriverSqlite3::loadKeypointSignaturesQuery(const std::list & ids, std::list & signatures, bool onlyParents) const +{ + ULOGGER_DEBUG("count=%d", ids.size()); + if(_ppDb && ids.size()) + { + std::string type; + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + std::multimap visualWords; + unsigned int loaded = 0; + + // Prepare the query... Get the map from signature and visual words + query << "SELECT id, weight, loopClosureId, imgWidth, imgHeight, visualWordId, pos_x, pos_y, laplacian, size, dir, hessian " + "FROM Map_SS_VW " + "INNER JOIN Signature " + "ON Signature.id = signatureId " + "WHERE type = 'surf' AND signatureId = ? "; + + if(onlyParents) + { + query << "AND loopClosureId = 0"; + } + query << " ORDER BY visualWordId"; // Needed for fast insertion below + query << ";"; + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + int id = 0; + int weight = 0; + int loopClosureId = 0; + int imgWidth = 0; + int imgHeight = 0; + int visualWordId = 0; + float pos_x = 0; + float pos_y = 0; + int laplacian = 0; + int size = 0; + float dir = 0; + float hessian = 0; + + for(std::list::const_iterator iter=ids.begin(); iter!=ids.end(); ++iter) + { + ULOGGER_DEBUG("Loading %d...", *iter); + // bind id + rc = sqlite3_bind_int(ppStmt, 1, *iter); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + id = 0; + weight = 0; + loopClosureId = 0; + imgWidth = 0; + imgHeight = 0; + visualWordId = 0; + pos_x = 0; + pos_y = 0; + laplacian = 0; + size = 0; + dir = 0; + hessian = 0; + visualWords.clear(); + + // Process the result if one + rc = sqlite3_step(ppStmt); + while(rc == SQLITE_ROW) + { + if(id==0) + { + id = sqlite3_column_int(ppStmt, 0); // Signature Id + weight = sqlite3_column_int(ppStmt, 1); // weight + loopClosureId = sqlite3_column_int(ppStmt, 2); // loopClosureId + imgWidth = sqlite3_column_int(ppStmt, 3); // imgWidth + imgHeight = sqlite3_column_int(ppStmt, 4); // imgheight + } + visualWordId = sqlite3_column_int(ppStmt, 5); + pos_x = sqlite3_column_double(ppStmt, 6); + pos_y = sqlite3_column_double(ppStmt, 7); + laplacian = sqlite3_column_int(ppStmt, 8); + size = sqlite3_column_int(ppStmt, 9); + dir = sqlite3_column_double(ppStmt, 10); + hessian = sqlite3_column_double(ppStmt, 11); + + visualWords.insert(visualWords.end(), std::pair(visualWordId, cv::KeyPoint(cv::Point2f(pos_x,pos_y),size,dir,hessian, 0, -1))); + rc = sqlite3_step(ppStmt); + } + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 2.2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + // create the signature + if(id) + { + ULOGGER_DEBUG("Creating %d with %d keypoints", *iter, visualWords.size()); + KeypointSignature * ss = new KeypointSignature(visualWords, id); + if(ss) + { + ss->setWeight(weight); + ss->setLoopClosureId(loopClosureId); + ss->setWidth(imgWidth); + ss->setHeight(imgHeight); + ss->setSaved(true); + signatures.push_back(ss); + } + else + { + UFATAL("?"); + } + ++loaded; + } + else if(!onlyParents) + { + ULOGGER_ERROR("Signature %d not found in database", *iter); + rc = sqlite3_finalize(ppStmt); + return false; + } + + //reset + rc = sqlite3_reset(ppStmt); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.3: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + } + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + + ULOGGER_DEBUG("Time=%fs", timer.ticks()); + + for(std::list::iterator i=signatures.begin(); i!=signatures.end(); ++i) + { + NeighborsMap neighbors; + this->loadNeighborsQuery((*i)->id(), neighbors); + (*i)->addNeighbors(neighbors); + } + ULOGGER_DEBUG("Time load neighbors=%fs", timer.ticks()); + + if(ids.size() != loaded && !onlyParents) + { + UERROR("Some signatures not found in database"); + } + + return true; + } + return false; +} + +bool DBDriverSqlite3::loadLastSignaturesQuery(std::list & signatures) const +{ + ULOGGER_DEBUG(""); + if(_ppDb) + { + std::string type; + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + std::list ids; + + // Get the map from signature and visual words + query << "SELECT s.id " + "FROM Signature AS s " + "WHERE s.timeEnter >= (SELECT MAX(timeEnter) FROM StatisticsAfterRun);" + /*"AND s.parentId IS NULL;"*/; + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + // Process the result if one + rc = sqlite3_step(ppStmt); + while(rc == SQLITE_ROW) + { + ids.push_back(sqlite3_column_int(ppStmt, 0)); // Signature id + rc = sqlite3_step(ppStmt); // next result... + } + + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + + ULOGGER_DEBUG("Loading %d signatures...", ids.size()); + Signature * s = 0; + int count = 0; + for(std::list::iterator i=ids.begin(); i!=ids.end(); ++i) + { + this->loadQuery(*i, &s); + if(s) + { + ++count; + signatures.push_back(s); + } + } + + ULOGGER_DEBUG("loaded=%d, Time=%fs", count, timer.ticks()); + + return true; + } + return false; +} + +// TODO DO IT IN A MORE EFFICiENT WAY !!!! +bool DBDriverSqlite3::loadQuery(VWDictionary * dictionary) const +{ + ULOGGER_DEBUG(""); + if(_ppDb && dictionary) + { + std::string type; + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + std::list visualWords; + + // Get the map from signature and visual words + query << "SELECT vw.id, vw.descriptorSize, vw.descriptor, m.signatureId " + "FROM VisualWord as vw " + "INNER JOIN Map_SS_VW as m " + "ON vw.id=m.visualWordId " + "INNER JOIN Signature as s " + "ON s.id=m.signatureId " + "WHERE s.timeEnter >= (SELECT MAX(timeEnter) FROM StatisticsAfterRun) " + "ORDER BY vw.id;"; + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + // Process the result if one + int id = 0; + int lastId = 0; + int descriptorSize = 0; + const void * descriptor = 0; + int dRealSize; + int signatureId; + rc = sqlite3_step(ppStmt); + int count = 0; + while(rc == SQLITE_ROW) + { + id = sqlite3_column_int(ppStmt, 0); // VisualWord Id + if(id>0) + { + if(id != lastId) + { + descriptorSize = sqlite3_column_int(ppStmt, 1); // VisualWord descriptor size + descriptor = sqlite3_column_blob(ppStmt, 2); // VisualWord descriptor array + dRealSize = sqlite3_column_bytes(ppStmt, 2); + } + lastId = id; + signatureId = sqlite3_column_int(ppStmt, 3); // Signature ref Id + if(dictionary->getWord(id) != 0) + { + // Use VWDictionary::addWordRef instead of VisualWord::addRef() + // This will increment _totalActiveReferences in the dictionary + dictionary->addWordRef(id, signatureId); + } + else + { + VisualWord * vw = new VisualWord(id, &((const float *)descriptor)[0], descriptorSize, signatureId); + if(vw) + { + vw->setSaved(true); + dictionary->addWord(vw); + } + else + { + ULOGGER_ERROR("Couldn't create a Visual word!?"); + } + } + } + else + { + ULOGGER_ERROR("Wrong word id ?!? (%d)", id); + } + if(++count % 5000 == 0) + { + ULOGGER_DEBUG("Loaded %d word references...", count); + } + rc = sqlite3_step(ppStmt); // next result... + } + + getLastVisualWordId(id); + dictionary->setLastWordId(id); + + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + + ULOGGER_DEBUG("Time=%fs", timer.ticks()); + return true; + } + return false; +} +/* +bool DBDriverSqlite3::loadWordsQuery(const std::list & wordIds, std::list & vws) const +{ + ULOGGER_DEBUG("size=%d", wordIds.size()); + if(_ppDb && wordIds.size()) + { + std::string type; + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + std::set loaded; + + // Get the map from signature and visual words + query << "SELECT vw.id, vw.laplacian, vw.descriptorSize, vw.descriptor " + "FROM VisualWord as vw " + "WHERE vw.id = "; + + for(std::list::const_iterator i=wordIds.begin(); i != wordIds.end();) + { + query << *i << " "; + + if(++i != wordIds.end()) + { + query << "or vw.id = "; + } + } + + query << ";"; + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + // Process the result if one + int id=0; + int laplacian; + int descriptorSize; + const void * descriptor; + int dRealSize; + rc = sqlite3_step(ppStmt); + while(rc == SQLITE_ROW) + { + id = sqlite3_column_int(ppStmt, 0); // VisualWord Id + laplacian = sqlite3_column_int(ppStmt, 1); // VisualWord laplacian + descriptorSize = sqlite3_column_int(ppStmt, 2); // VisualWord descriptor size + descriptor = sqlite3_column_blob(ppStmt, 3); // VisualWord descriptor array + dRealSize = sqlite3_column_bytes(ppStmt, 3); + + VisualWord * vw = new VisualWord(id, &((const float *)descriptor)[0], descriptorSize, laplacian); + if(vw) + { + vw->setSaved(true); + } + vws.push_back(vw); + loaded.insert(loaded.end(), id); + + rc = sqlite3_step(ppStmt); // next result... + } + + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + + ULOGGER_DEBUG("Time=%fs", timer.ticks()); + + if(wordIds.size() != loaded.size()) + { + UERROR("Query (%d) doesn't match loaded words (%d)", wordIds.size(), loaded.size()); + UDEBUG("Query = %s", query.str().c_str()); + for(std::list::const_iterator iter = wordIds.begin(); iter!=wordIds.end(); ++iter) + { + if(loaded.find(*iter) == loaded.end()) + { + UDEBUG("Not found word %d", *iter); + } + } + + } + + return true; + } + return false; +}*/ + +//may be slower than the previous version but don't have a limit of words that can be loaded at the same time +bool DBDriverSqlite3::loadWordsQuery(const std::list & wordIds, std::list & vws) const +{ + ULOGGER_DEBUG("size=%d", wordIds.size()); + if(_ppDb && wordIds.size()) + { + std::string type; + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + std::set loaded; + + // Get the map from signature and visual words + query << "SELECT vw.id, vw.descriptorSize, vw.descriptor " + "FROM VisualWord as vw " + "WHERE vw.id = ?;"; + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + int id=0; + int descriptorSize; + const void * descriptor; + int dRealSize; + for(std::list::const_iterator iter=wordIds.begin(); iter!=wordIds.end(); ++iter) + { + // bind id + rc = sqlite3_bind_int(ppStmt, 1, *iter); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + // Process the result if one + rc = sqlite3_step(ppStmt); + while(rc == SQLITE_ROW) + { + id = sqlite3_column_int(ppStmt, 0); // VisualWord Id + descriptorSize = sqlite3_column_int(ppStmt, 1); // VisualWord descriptor size + descriptor = sqlite3_column_blob(ppStmt, 2); // VisualWord descriptor array + dRealSize = sqlite3_column_bytes(ppStmt, 2); + + VisualWord * vw = new VisualWord(id, &((const float *)descriptor)[0], descriptorSize); + if(vw) + { + vw->setSaved(true); + } + vws.push_back(vw); + loaded.insert(loaded.end(), id); + + rc = sqlite3_step(ppStmt); // next result... + } + + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 2.2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + rc = sqlite3_reset(ppStmt); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.3: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + } + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + + ULOGGER_DEBUG("Time=%fs", timer.ticks()); + + if(wordIds.size() != loaded.size()) + { + UERROR("Query (%d) doesn't match loaded words (%d)", wordIds.size(), loaded.size()); + for(std::list::const_iterator iter = wordIds.begin(); iter!=wordIds.end(); ++iter) + { + if(loaded.find(*iter) == loaded.end()) + { + UDEBUG("Not found word %d", *iter); + } + } + + } + + return true; + } + return false; +} + +bool DBDriverSqlite3::loadQuery(int wordId, VisualWord ** vw) const +{ + *vw = 0; + //ULOGGER_DEBUG("DBDriverSqlite3::load(int, VisualWord **)"); + if(_ppDb) + { + std::string type; + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + + // Get the map from signature and visual words + query << "SELECT vw.id, vw.descriptorSize, vw.descriptor " + "FROM VisualWord AS vw " + "WHERE vw.id = " << wordId << ";"; + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + // Process the result if one + int id=0; + int descriptorSize; + const void * descriptor; + int dRealSize; + + rc = sqlite3_step(ppStmt); + + if(rc == SQLITE_ROW) + { + id = sqlite3_column_int(ppStmt, 0); // VisualWord Id + descriptorSize = sqlite3_column_int(ppStmt, 1); // VisualWord descriptor size + descriptor = sqlite3_column_blob(ppStmt, 2); // VisualWord descriptor array + dRealSize = sqlite3_column_bytes(ppStmt, 2); + + rc = sqlite3_step(ppStmt); + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("Supposed to received only 1 result... wordId=%d", wordId); + rc = sqlite3_finalize(ppStmt); + return false; + } + + //Create the word + *vw = new VisualWord(id, &((const float *)descriptor)[0], descriptorSize); + if(*vw) + { + (*vw)->setSaved(true); + } + } + else + { + ULOGGER_ERROR("No result !?! from the DB, wordId=%d", wordId); + } + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + + //ULOGGER_DEBUG("DBDriverSqlite3::load(int, VisualWord **) Time=%fs", timer.ticks()); + return true; + } + return false; +} + +//only returns neighbors with no parentId and no loopClosureId +bool DBDriverSqlite3::loadNeighborsQuery(int signatureId, NeighborsMap & neighbors) const +{ + neighbors.clear(); + if(_ppDb) + { + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::stringstream query; + + query << "SELECT nid, actionSize, actions FROM Neighbor LEFT JOIN Signature ON Signature.id = nid " + << "WHERE sid = " << signatureId << " AND " + << "(Signature.loopClosureId = 0 OR Signature.loopClosureId is null) "; + // << "ORDER BY Signature.weight DESC, Signature.id DESC;"; + + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + int nid; + int actionSize; + const float * data; + int dataSize; + std::list > actions; + + // Process the result if one + rc = sqlite3_step(ppStmt); + while(rc == SQLITE_ROW) + { + nid = sqlite3_column_int(ppStmt, 0); + actionSize = sqlite3_column_int(ppStmt, 1); + data = (const float *)sqlite3_column_blob(ppStmt, 2); // actions array + dataSize = sqlite3_column_bytes(ppStmt, 2)/sizeof(float); + + for(int i=0; i action(actionSize); + for(int j=0; j > >(nid, actions)); // nid + rc = sqlite3_step(ppStmt); + } + if(rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + return true; + } + return false; +} + + +bool DBDriverSqlite3::updateQuery(const std::list & signatures) const +{ + if(_ppDb && signatures.size()) + { + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + Signature * s = 0; + + const char * query = "UPDATE Signature SET weight=?, loopClosureId=?, timeEnter = DATETIME('NOW') WHERE id=?;"; + rc = sqlite3_prepare_v2(_ppDb, query, -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + for(std::list::const_iterator i=signatures.begin(); i!=signatures.end(); ++i) + { + s = *i; + if(s) + { + rc = sqlite3_bind_int(ppStmt, 1, s->getWeight()); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1.4: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_int(ppStmt, 2, s->getLoopClosureId()); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1.5: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_int(ppStmt, 3, s->id()); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1.6: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + //step + rc=sqlite3_step(ppStmt); + if (rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 1.10: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + rc = sqlite3_reset(ppStmt); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1.11: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + } + } + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1.12: %s", sqlite3_errmsg(_ppDb)); + return false; + } + + ULOGGER_DEBUG("Update Signature table, Time=%fs", timer.ticks()); + + // Update neighbors part1 + query = "DELETE FROM Neighbor WHERE sid=?;"; + rc = sqlite3_prepare_v2(_ppDb, query, -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + for(std::list::const_iterator j=signatures.begin(); j!=signatures.end(); ++j) + { + rc = sqlite3_bind_int(ppStmt, 1, (*j)->id()); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + rc=sqlite3_step(ppStmt); + if (rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error sid=%d: %s", (*j)->id(), sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + rc = sqlite3_reset(ppStmt); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error, sid=%d: %s", (*j)->id(), sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + } + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error: %s", sqlite3_errmsg(_ppDb)); + return false; + } + + // Update neighbors part2 + query = "INSERT INTO Neighbor(sid, nid, actionSize, actions) VALUES(?,?,?,?);"; + rc = sqlite3_prepare_v2(_ppDb, query, -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + for(std::list::const_iterator j=signatures.begin(); j!=signatures.end(); ++j) + { + const NeighborsMap & neighbors = (*j)->getNeighbors(); + for(NeighborsMap::const_iterator i=neighbors.begin(); i!=neighbors.end(); ++i) + { + rc = sqlite3_bind_int(ppStmt, 1, (*j)->id()); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_int(ppStmt, 2, i->first); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + unsigned int actionSize = i->second.size()>0?i->second.front().size():0; + if(actionSize) + { + rc = sqlite3_bind_int(ppStmt, 3, actionSize); + } + else + { + rc = sqlite3_bind_null(ppStmt, 3); + } + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.3: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + if(actionSize) + { + //Concatenate actions + std::vector actions; + for(std::list >::const_iterator iter=i->second.begin(); iter!=i->second.end(); ++iter) + { + if(iter->size() != actionSize) + { + UERROR("Actuator states must be all the same size! (actionSize=%d != currentAction%d)", actionSize, iter->size()); + } + actions.insert(actions.end(), iter->begin(), iter->end()); + } + rc = sqlite3_bind_blob(ppStmt, 4, (unsigned char *)actions.data(), actions.size()*sizeof(float), SQLITE_STATIC); + } + else + { + rc = sqlite3_bind_null(ppStmt, 4); + } + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.4: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + + rc=sqlite3_step(ppStmt); + if (rc != SQLITE_DONE) + { + // error "constraint failed" is ok, because we do here a "INSERT IF NOT EXIST". + if(strcmp("constraint failed", sqlite3_errmsg(_ppDb)) != 0) + { + ULOGGER_ERROR("DB error, sid=%d, nid=%d: %s", (*j)->id(), i->first, sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + } + + rc = sqlite3_reset(ppStmt); + if (rc != SQLITE_OK) + { + // error "columns sid, nid are not unique" is ok, because we do here a "INSERT IF NOT EXIST". + if(strcmp("columns sid, nid are not unique", sqlite3_errmsg(_ppDb)) != 0) + { + ULOGGER_ERROR("DB error, sid=%d, nid=%d: %s", (*j)->id(), i->first, sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + } + } + } + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error: %s", sqlite3_errmsg(_ppDb)); + return false; + } + + + ULOGGER_DEBUG("Update Neighbors Time=%fs", timer.ticks()); + return true; + } + return false; +} + +// +// TODO DO IT IN A MORE EFFICiENT WAY !!!! (if it is possible...) +bool DBDriverSqlite3::changeWordsRefQuery(const std::map & refsToChange) const +{ + ULOGGER_DEBUG("Changing %d references...", refsToChange.size()); + if(_ppDb && refsToChange.size()) + { + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + + const char * query = "UPDATE Map_SS_VW SET visualWordId = ? WHERE visualWordId = ?;"; + + rc = sqlite3_prepare_v2(_ppDb, query, -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + int count=0; + for(std::map::const_iterator i=refsToChange.begin(); i!=refsToChange.end(); ++i) + { + rc = sqlite3_bind_int(ppStmt, 1, (*i).second); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1.1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_int(ppStmt, 2, (*i).first); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1.2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + //step + rc=sqlite3_step(ppStmt); + if (rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 1.10: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + rc = sqlite3_reset(ppStmt); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1.11: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + if(++count % 5000 == 0) + { + ULOGGER_DEBUG("Changed %d references...", count); + } + } + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 5: %s", sqlite3_errmsg(_ppDb)); + return false; + } + ULOGGER_DEBUG("Time=%fs", timer.ticks()); + return true; + } + return false; +} + +bool DBDriverSqlite3::deleteWordsQuery(const std::vector & ids) const +{ + if(_ppDb && ids.size()) + { + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + + const char * query = "DELETE FROM VisualWord WHERE id = ?;"; + + rc = sqlite3_prepare_v2(_ppDb, query, -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + for(unsigned int i=0; i & words = ss->getWords(); + + // Image compressed + CvMat * compressed = 0; + + if(ss->getImage()) + { + // Create a new entry in table KeypointSignature + query << "INSERT INTO Signature(id, type, weight, loopClosureId, imgWidth, imgHeight, image) " + "values(" + << ss->id() << ",'surf'," + << ss->getWeight() << "," + << ss->getLoopClosureId() << "," + << ss->getWidth() << "," + << ss->getHeight() << "," + << "?);"; + } + else + { + // Create a new entry in table KeypointSignature + query << "INSERT INTO Signature(id, type, weight, loopClosureId, imgWidth, imgHeight) " + "values(" + << ss->id() << ",'surf'," + << ss->getWeight() << "," + << ss->getLoopClosureId() << "," + << ss->getWidth() << "," + << ss->getHeight() << ");"; + } + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + if(ss->getImage()) + { + compressed = ss->compressImage(ss->getImage()); + if(compressed) + { + rc = sqlite3_bind_blob(ppStmt, 1, compressed->data.ptr, compressed->width*sizeof(uchar), SQLITE_STATIC); + } + else + { + UERROR("Compression failed!"); + rc = sqlite3_bind_blob(ppStmt, 6, 0, 0, SQLITE_STATIC); + } + + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1.1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + if(compressed) + { + cvReleaseMat(&compressed); + } + return false; + } + } + + rc=sqlite3_step(ppStmt); + if(compressed) + { + cvReleaseMat(&compressed);// release before any return false + } + if (rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 1.2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1.3: %s", sqlite3_errmsg(_ppDb)); + return false; + } + + // Create new entries in table Map_SS_VW + if(words.size()>0) + { + query.str(""); + query << "INSERT INTO Map_SS_VW(signatureId, visualWordId, pos_x, pos_y, laplacian, size, dir, hessian) VALUES(?,?,?,?,?,?,?,?);"; + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + for(std::multimap::const_iterator i=words.begin(); i!=words.end(); ++i) + { + const cv::KeyPoint & kp = (*i).second; + rc = sqlite3_bind_int(ppStmt, 1, ss->id()); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_int(ppStmt, 2, (*i).first); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_double(ppStmt, 3, kp.pt.x); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.3: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_double(ppStmt, 4, kp.pt.y); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.4: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_int(ppStmt, 5, uSign(kp.response)); // laplacian + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.5: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_int(ppStmt, 6, kp.size); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.6: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_double(ppStmt, 7, kp.angle); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.7: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_double(ppStmt, 8, kp.response); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.8: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + rc=sqlite3_step(ppStmt); + if (rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 2.9: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + rc = sqlite3_reset(ppStmt); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.10: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + } + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.11: %s", sqlite3_errmsg(_ppDb)); + return false; + } + } + else + { + ULOGGER_WARN("Empty words ref for sign %d : size(words)=%d", ss->id(), words.size()); + } + + // Create new entries in table Neighbor + const NeighborsMap & neighbors = ss->getNeighbors(); + if(neighbors.size()) + { + query.str(""); + query << "INSERT INTO Neighbor(sid, nid, actionSize, actions) VALUES(" << ss->id() <<",?,?,?);"; + rc = sqlite3_prepare_v2(_ppDb, query.str().c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + for(NeighborsMap::const_iterator i=neighbors.begin(); i!=neighbors.end(); ++i) + { + rc = sqlite3_bind_int(ppStmt, 1, ss->id()); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3.1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_int(ppStmt, 2, i->first); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3.2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + unsigned int actionSize = i->second.size()>0?i->second.front().size():0; + if(actionSize) + { + rc = sqlite3_bind_int(ppStmt, 3, actionSize); + } + else + { + rc = sqlite3_bind_null(ppStmt, 3); + } + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.3: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + if(actionSize) + { + //Concatenate actions + std::vector actions; + for(std::list >::const_iterator iter=i->second.begin(); iter!=i->second.end(); ++iter) + { + if(iter->size() != actionSize) + { + UERROR("Actuator states must be all the same size! (actionSize=%d != currentAction%d)", actionSize, iter->size()); + } + actions.insert(actions.end(), iter->begin(), iter->end()); + } + rc = sqlite3_bind_blob(ppStmt, 4, (unsigned char *)actions.data(), actions.size()*sizeof(float), SQLITE_STATIC); + } + else + { + rc = sqlite3_bind_null(ppStmt, 4); + } + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.4: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + rc=sqlite3_step(ppStmt); + if (rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 3.2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + rc = sqlite3_reset(ppStmt); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3.3: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + } + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3.4: %s", sqlite3_errmsg(_ppDb)); + return false; + } + } + else + { + ULOGGER_WARN("Empty neighbors ref for sign %d", ss->id()); + } + + ULOGGER_DEBUG("Time=%fs", timer.ticks()); + + return true; + } + return false; +} + +bool DBDriverSqlite3::saveQuery(const std::list & signatures) const +{ + ULOGGER_DEBUG("KeypointSignature"); + if(_ppDb && signatures.size()) + { + std::string type; + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + KeypointSignature * s = 0; + CvMat * compressed = 0; + + // Signature table + const char * query = "INSERT INTO Signature(id, type, weight, loopClosureId, imgWidth, imgHeight, image) VALUES(?,'surf',?,?,?,?,?);"; + rc = sqlite3_prepare_v2(_ppDb, query, -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + for(std::list::const_iterator i=signatures.begin(); i!=signatures.end(); ++i) + { + s = *i; + if(s) + { + rc = sqlite3_bind_int(ppStmt, 1, s->id()); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1.1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_int(ppStmt, 2, s->getWeight()); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1.5: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_int(ppStmt, 3, s->getLoopClosureId()); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1.6: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_int(ppStmt, 4, s->getWidth()); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1.7: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_int(ppStmt, 5, s->getHeight()); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1.8: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + if(s->getImage()) + { + compressed = s->compressImage(s->getImage()); + if(compressed) + { + rc = sqlite3_bind_blob(ppStmt, 6, compressed->data.ptr, compressed->width*sizeof(uchar), SQLITE_STATIC); + } + else + { + UERROR("Compression failed!"); + rc = sqlite3_bind_blob(ppStmt, 6, 0, 0, SQLITE_STATIC); + } + } + else + { + rc = sqlite3_bind_null(ppStmt, 6); + } + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1.9: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + if(compressed) + { + cvReleaseMat(&compressed); + } + return false; + } + + //step + rc=sqlite3_step(ppStmt); + if(compressed) // Release it before any return false + { + cvReleaseMat(&compressed); + compressed = 0; + } + if (rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 1.10: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + rc = sqlite3_reset(ppStmt); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1.11: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + } + } + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 5: %s", sqlite3_errmsg(_ppDb)); + return false; + } + + + // Create new entries in table Map_SS_VW + query = "INSERT INTO Map_SS_VW(signatureId, visualWordId, pos_x, pos_y, laplacian, size, dir, hessian) VALUES(?,?,?,?,?,?,?,?);"; + rc = sqlite3_prepare_v2(_ppDb, query, -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + for(std::list::const_iterator i=signatures.begin(); i!=signatures.end(); ++i) + { + s = *i; + for(std::multimap::const_iterator i=s->getWords().begin(); i!=s->getWords().end(); ++i) + { + const cv::KeyPoint & kp = (*i).second; + rc = sqlite3_bind_int(ppStmt, 1, s->id()); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_int(ppStmt, 2, (*i).first); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_double(ppStmt, 3, kp.pt.x); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.3: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_double(ppStmt, 4, kp.pt.y); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.4: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_int(ppStmt, 5, uSign(kp.response)); // laplacian + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.5: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_int(ppStmt, 6, kp.size); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.6: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_double(ppStmt, 7, kp.angle); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.7: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_double(ppStmt, 8, kp.response); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.8: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + rc=sqlite3_step(ppStmt); + if (rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 2.9: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + rc = sqlite3_reset(ppStmt); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.10: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + } + } + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.11: %s", sqlite3_errmsg(_ppDb)); + return false; + } + + // Create new entries in table Neighbor + query = "INSERT INTO Neighbor(sid, nid, actionSize, actions) VALUES(?,?,?,?);"; + rc = sqlite3_prepare_v2(_ppDb, query, -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + for(std::list::const_iterator j=signatures.begin(); j!=signatures.end(); ++j) + { + const NeighborsMap & neighbors = (*j)->getNeighbors(); + for(NeighborsMap::const_iterator i=neighbors.begin(); i!=neighbors.end(); ++i) + { + rc = sqlite3_bind_int(ppStmt, 1, (*j)->id()); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3.1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_int(ppStmt, 2, i->first); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3.2: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + unsigned int actionSize = i->second.size()>0?i->second.front().size():0; + if(actionSize) + { + rc = sqlite3_bind_int(ppStmt, 3, actionSize); + } + else + { + rc = sqlite3_bind_null(ppStmt, 3); + } + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.3: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + if(actionSize) + { + //Concatenate actions + std::vector actions; + for(std::list >::const_iterator iter=i->second.begin(); iter!=i->second.end(); ++iter) + { + if(iter->size() != actionSize) + { + UERROR("Actuator states must be all the same size! (actionSize=%d != currentAction%d)", actionSize, iter->size()); + } + actions.insert(actions.end(), iter->begin(), iter->end()); + } + rc = sqlite3_bind_blob(ppStmt, 4, (unsigned char *)actions.data(), actions.size()*sizeof(float), SQLITE_STATIC); + } + else + { + rc = sqlite3_bind_null(ppStmt, 4); + } + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.4: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + rc=sqlite3_step(ppStmt); + if (rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 3.3: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + rc = sqlite3_reset(ppStmt); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3.4: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + } + } + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 3.5: %s", sqlite3_errmsg(_ppDb)); + return false; + } + + ULOGGER_DEBUG("Time=%fs", timer.ticks()); + + return true; + } + return false; +} + +/* BUG : there is a problem with the code at line indicated below... So it is commented.*/ +bool DBDriverSqlite3::saveQuery(const std::vector & visualWords) const +{ + ULOGGER_DEBUG("visualWords size=%d", visualWords.size()); + if(_ppDb) + { + std::string type; + UTimer timer; + timer.start(); + int rc = SQLITE_OK; + sqlite3_stmt * ppStmt = 0; + std::string query; + + // Create new entries in table Map_SS_VW + if(visualWords.size()>0) + { + query = std::string("INSERT INTO VisualWord(id, descriptorSize, descriptor) VALUES(?,?,?);"); + rc = sqlite3_prepare_v2(_ppDb, query.c_str(), -1, &ppStmt, 0); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + for(std::vector::const_iterator iter=visualWords.begin(); iter!=visualWords.end(); ++iter) + { + const VisualWord * w = *iter; + if(w && !w->isSaved()) + { + rc = sqlite3_bind_int(ppStmt, 1, w->id()); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.1: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_int(ppStmt, 2, w->getDim()); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.3: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + rc = sqlite3_bind_blob(ppStmt, 3, (unsigned char *)w->getDescriptor(), w->getDim()*sizeof(float), SQLITE_STATIC); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 2.4: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + //execute query + rc=sqlite3_step(ppStmt); + if (rc != SQLITE_DONE) + { + ULOGGER_ERROR("DB error 3: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + + rc = sqlite3_reset(ppStmt); + if (rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 4: %s", sqlite3_errmsg(_ppDb)); + rc = sqlite3_finalize(ppStmt); + return false; + } + } + } + // Finalize (delete) the statement + rc = sqlite3_finalize(ppStmt); + if(rc != SQLITE_OK) + { + ULOGGER_ERROR("DB error 5: %s", sqlite3_errmsg(_ppDb)); + return false; + } + } + else + { + //ULOGGER_DEBUG("DBDriverSqlite3::save(std::list) Warning : Empty words ref : size(words)=%d", words.size()); + } + + + ULOGGER_DEBUG("Time=%fs", timer.ticks()); + + return true; + } + return false; +} + +} // namespace rtabmap diff --git a/corelib/src/DBDriverSqlite3.h b/corelib/src/DBDriverSqlite3.h new file mode 100644 index 0000000000..4d2b572275 --- /dev/null +++ b/corelib/src/DBDriverSqlite3.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef DBDRIVERSQLITE3_H_ +#define DBDRIVERSQLITE3_H_ + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines +#include "rtabmap/core/DBDriver.h" + +#include + +namespace rtabmap { + +class RTABMAP_EXP DBDriverSqlite3: public DBDriver { +public: + DBDriverSqlite3(const ParametersMap & parameters = ParametersMap()); + virtual ~DBDriverSqlite3(); + + virtual void parseParameters(const ParametersMap & parameters); + void setDbInMemory(bool dbInMemory); + void setJournalMode(int journalMode); + void setCacheSize(unsigned int cacheSize); + +private: + virtual bool connectDatabaseQuery(const std::string & url); + virtual void disconnectDatabaseQuery(); + virtual bool isConnectedQuery() const; + virtual long getMemoryUsedQuery() const; // In bytes + + virtual bool executeNoResultQuery(const std::string & sql) const; + + virtual bool changeWordsRefQuery(const std::map & refsToChange) const; // + virtual bool deleteWordsQuery(const std::vector & ids) const; + virtual bool getNeighborIdsQuery(int signatureId, std::set & neighbors) const; + virtual bool getWeightQuery(int signatureId, int & weight) const; + virtual bool getLoopClosureIdQuery(int signatureId, int & loopId) const; + virtual bool addNeighborQuery(int id, int neighbor, const std::list > & actuatorStates) const; + + virtual bool saveQuery(const std::vector & visualWords) const; + virtual bool updateQuery(const std::list & signatures) const; + virtual bool saveQuery(const KeypointSignature * ss) const; + virtual bool saveQuery(const std::list & signatures) const; + + // Load objects + virtual bool loadQuery(VWDictionary * dictionary) const; + virtual bool loadLastSignaturesQuery(std::list & signatures) const; + virtual bool loadQuery(int signatureId, Signature ** s) const; + virtual bool loadQuery(int wordId, VisualWord ** vw) const; + virtual bool loadQuery(int signatureId, KeypointSignature * ss) const; + virtual bool loadKeypointSignaturesQuery(const std::list & ids, std::list & signatures, bool onlyParents = false) const; + virtual bool loadWordsQuery(const std::list & wordIds, std::list & vws) const; + virtual bool loadNeighborsQuery(int signatureId, std::map > > & neighbors) const; + + virtual bool getImageCompressedQuery(int id, CvMat ** compressed) const; + virtual bool getAllSignatureIdsQuery(std::set & ids) const; + virtual bool getLastSignatureIdQuery(int & id) const; + virtual bool getLastVisualWordIdQuery(int & id) const; + virtual bool getSurfNiQuery(int signatureId, int & ni) const; + virtual bool getChildrenIdsQuery(int signatureId, std::list & ids) const; + virtual bool getHighestWeightedSignaturesQuery(unsigned int count, std::multimap & ids) const; + +private: + int loadOrSaveDb(sqlite3 *pInMemory, const std::string & fileName, int isSave) const; + +private: + sqlite3 * _ppDb; + bool _dbInMemory; + unsigned int _cacheSize; + int _journalMode; +}; + +} + +#endif /* DBDRIVERSQLITE3_H_ */ diff --git a/corelib/src/EpipolarGeometry.cpp b/corelib/src/EpipolarGeometry.cpp new file mode 100644 index 0000000000..999a70a509 --- /dev/null +++ b/corelib/src/EpipolarGeometry.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "rtabmap/core/EpipolarGeometry.h" +#include "utilite/ULogger.h" + +#include +#include + +namespace rtabmap +{ + +//Epipolar geometry +void findEpipolesFromF(const cv::Mat & fundamentalMatrix, cv::Vec3d & e1, cv::Vec3d & e2) +{ + if(fundamentalMatrix.rows != 3 || fundamentalMatrix.cols != 3) + { + ULOGGER_ERROR("The matrix is not the good size..."); + return; + } + + if(fundamentalMatrix.type() != CV_64FC1) + { + ULOGGER_ERROR("The matrix is not the good type..."); + return; + } + + CvMat * w = cvCreateMat(3, 3, CV_64FC1); + CvMat * u = cvCreateMat(3, 3, CV_64FC1); + CvMat * v = cvCreateMat(3, 3, CV_64FC1); + + CvMat f = fundamentalMatrix; + cvSVD(&f, w, u, v); + + // v is for image 1 + // u is for image 2 + + e1[0] = v->data.db[0*3+2];// /v->data.db[2*3+2]; + e1[1] = v->data.db[1*3+2];// /v->data.db[2*3+2]; + e1[2] = v->data.db[2*3+2];// /v->data.db[2*3+2]; + + e2[0] = u->data.db[0*3+2];// /u->data.db[2*3+2]; + e2[1] = u->data.db[1*3+2];// /u->data.db[2*3+2]; + e2[2] = u->data.db[2*3+2];// /u->data.db[2*3+2]; + + cvReleaseMat(&w); + cvReleaseMat(&u); + cvReleaseMat(&v); +} + +// P2 = [M | t] = [[e']_x * F | e'] +void findPFromF(const cv::Mat & fundamentalMatrix, cv::Mat & p2, cv::Vec3d e2) +{ + if(p2.rows != 3 || p2.cols != 4 || fundamentalMatrix.rows != 3 || fundamentalMatrix.cols != 3) + { + ULOGGER_ERROR("Matrices are not the good size... "); + return; + } + + if(p2.type()!= CV_64FC1 || fundamentalMatrix.type() != CV_64FC1) + { + ULOGGER_ERROR("Matrices are not the good type..."); + return; + } + + if(e2[0] == 0 && e2[1] == 0 && e2[2] == 0) + { + cv::Vec3d e1; + findEpipolesFromF(fundamentalMatrix, e1, e2); + } + + double e2_sd[3*3] = { 0., -e2[2], e2[1], + e2[2], 0., -e2[0], + -e2[1], e2[0], 0. }; + CvMat e2_smt = cvMat( 3, 3, CV_64FC1, e2_sd ); + cv::Mat e2_sm(&e2_smt); //; + + cv::Mat m = e2_sm*fundamentalMatrix; + + p2.at(0,0) = m.at(0,0); + p2.at(0,1) = m.at(0,1); + p2.at(0,2) = m.at(0,2); + p2.at(1,0) = m.at(1,0); + p2.at(1,1) = m.at(1,1); + p2.at(1,2) = m.at(1,2); + p2.at(2,0) = m.at(2,0); + p2.at(2,1) = m.at(2,1); + p2.at(2,2) = m.at(2,2); + + p2.at(0,3) = e2[0]; + p2.at(1,3) = e2[1]; + p2.at(2,3) = e2[2]; +} + +} // namespace rtabmap diff --git a/corelib/src/KeypointDescriptor.cpp b/corelib/src/KeypointDescriptor.cpp new file mode 100644 index 0000000000..cfd464d57c --- /dev/null +++ b/corelib/src/KeypointDescriptor.cpp @@ -0,0 +1,541 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "rtabmap/core/KeypointDescriptor.h" +#include "utilite/UStl.h" +#include "utilite/UConversion.h" +#include "utilite/ULogger.h" +#include "utilite/UMath.h" +#include "utilite/ULogger.h" +#include +#include +#include + +#define OPENCV_SURF_GPU CV_MAJOR_VERSION >= 2 and CV_MINOR_VERSION >=2 and CV_SUBMINOR_VERSION>=1 + +namespace rtabmap { + +KeypointDescriptor::KeypointDescriptor(const ParametersMap & parameters, KeypointDescriptor * childDescriptor) : + _childDescriptor(childDescriptor) +{ + this->parseParameters(parameters); +} + +KeypointDescriptor::~KeypointDescriptor() +{ + if(_childDescriptor) + { + delete _childDescriptor; + } +} + +void KeypointDescriptor::parseParameters(const ParametersMap & parameters) +{ + if(_childDescriptor) + { + _childDescriptor->parseParameters(parameters); + } +} + +std::list > KeypointDescriptor::generateDescriptors(const IplImage * image, const std::list & keypoints) const +{ + ULOGGER_DEBUG(""); + // see decorator pattern... + std::list > descriptors = this->_generateDescriptors(image, keypoints); + std::list > childDescriptors; + if(_childDescriptor) + { + childDescriptors = _childDescriptor->generateDescriptors(image, keypoints); + + if(childDescriptors.size() && childDescriptors.size() == descriptors.size()) + { + std::list >::iterator iterDesc = descriptors.begin(); + std::list >::iterator iterChild = childDescriptors.begin(); + for(; iterDesc!=descriptors.end(); ++iterDesc, ++iterChild) + { + iterDesc->insert(iterDesc->end(), iterChild->begin(), iterChild->end()); + } + } + } + return descriptors; +} + +void KeypointDescriptor::setChildDescriptor(KeypointDescriptor * childDescriptor) +{ + if(_childDescriptor) + { + delete _childDescriptor; + } + _childDescriptor = childDescriptor; +} + +////////////////////////// +//SURFDescriptor +////////////////////////// +SURFDescriptor::SURFDescriptor(const ParametersMap & parameters, KeypointDescriptor * childDescriptor) : + KeypointDescriptor(parameters, childDescriptor) +{ + _surf.hessianThreshold = Parameters::defaultSURFHessianThreshold(); + _surf.extended = Parameters::defaultSURFExtended(); + _surf.nOctaveLayers = Parameters::defaultSURFOctaveLayers(); + _surf.nOctaves = Parameters::defaultSURFOctaves(); + _gpuVersion = Parameters::defaultSURFGpuVersion(); + _upright = Parameters::defaultSURFUpright(); + this->parseParameters(parameters); +} + +SURFDescriptor::~SURFDescriptor() +{ +} + +void SURFDescriptor::parseParameters(const ParametersMap & parameters) +{ + ParametersMap::const_iterator iter; + if((iter=parameters.find(Parameters::kSURFExtended())) != parameters.end()) + { + _surf.extended = uStr2Bool((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kSURFHessianThreshold())) != parameters.end()) + { + _surf.hessianThreshold = std::atof((*iter).second.c_str()); // is it needed for the descriptor? + } + if((iter=parameters.find(Parameters::kSURFOctaveLayers())) != parameters.end()) + { + _surf.nOctaveLayers = std::atoi((*iter).second.c_str()); // is it needed for the descriptor? + } + if((iter=parameters.find(Parameters::kSURFOctaves())) != parameters.end()) + { + _surf.nOctaves = std::atoi((*iter).second.c_str()); // is it needed for the descriptor? + } + if((iter=parameters.find(Parameters::kSURFGpuVersion())) != parameters.end()) + { + _gpuVersion = uStr2Bool((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kSURFUpright())) != parameters.end()) + { + _upright = uStr2Bool((*iter).second.c_str()); + } + KeypointDescriptor::parseParameters(parameters); +} + +std::list > SURFDescriptor::_generateDescriptors(const IplImage * image, const std::list & keypoints) const +{ + ULOGGER_DEBUG(""); + std::list > descriptors; + if(!image) + { + ULOGGER_ERROR("Image is null ?!?"); + return descriptors; + } + // SURF support only grayscale images + IplImage * imageGrayScale = 0; + if(image->nChannels != 1 || image->depth != IPL_DEPTH_8U) + { + imageGrayScale = cvCreateImage(cvSize(image->width,image->height), IPL_DEPTH_8U, 1); + cvCvtColor(image, imageGrayScale, CV_BGR2GRAY); + } + cv::Mat img; + if(imageGrayScale) + { + img = cv::Mat(imageGrayScale); + } + else + { + img = cv::Mat(image); + } + cv::Mat mask; + std::vector k = uListToVector(keypoints); + std::vector d; +#if OPENCV_SURF_GPU + if(_gpuVersion) + { + cv::gpu::GpuMat imgGpu(img); + cv::gpu::GpuMat descriptorsGpu; + cv::gpu::GpuMat keypointsGpu; + cv::gpu::SURF_GPU surfGpu(_surf.hessianThreshold, _surf.nOctaves, _surf.nOctaveLayers, _surf.extended, 0.01f, _upright); + surfGpu.uploadKeypoints(k, keypointsGpu); + surfGpu(imgGpu, cv::gpu::GpuMat(), keypointsGpu, descriptorsGpu, true); + surfGpu.downloadDescriptors(descriptorsGpu, d); + } + else + { + _surf(img, mask, k, d, true); // Opencv surf descriptors + } +#else + _surf(img, mask, k, d, true); // Opencv surf descriptors +#endif + + unsigned int dim = _surf.descriptorSize(); + for(unsigned int i=0; i(d.begin()+i, d.begin()+i+dim)); + } + if(imageGrayScale) + { + cvReleaseImage(&imageGrayScale); + } + return descriptors; +} + +////////////////////////// +//SIFTDescriptor +////////////////////////// +SIFTDescriptor::SIFTDescriptor(const ParametersMap & parameters, KeypointDescriptor * childDescriptor) : + KeypointDescriptor(parameters, childDescriptor) +{ + this->parseParameters(parameters); +} + +SIFTDescriptor::~SIFTDescriptor() +{ +} + +void SIFTDescriptor::parseParameters(const ParametersMap & parameters) +{ + ParametersMap::const_iterator iter; + KeypointDescriptor::parseParameters(parameters); +} + +std::list > SIFTDescriptor::_generateDescriptors(const IplImage * image, const std::list & keypoints) const +{ + ULOGGER_DEBUG(""); + std::list > descriptors; + if(!image) + { + ULOGGER_ERROR("Image is null ?!?"); + return descriptors; + } + // SURF support only grayscale images + IplImage * imageGrayScale = 0; + if(image->nChannels != 1 || image->depth != IPL_DEPTH_8U) + { + imageGrayScale = cvCreateImage(cvSize(image->width,image->height), IPL_DEPTH_8U, 1); + cvCvtColor(image, imageGrayScale, CV_BGR2GRAY); + } + cv::Mat img; + if(imageGrayScale) + { + img = cv::Mat(imageGrayScale); + } + else + { + img = cv::Mat(image); + } + cv::Mat mask; + std::vector k = uListToVector(keypoints); + cv::Mat d; + cv::SIFT sift(_commonParams, cv::SIFT::DetectorParams(), _descriptorParams); + sift(img, mask, k, d, true); // Opencv surf descriptors + unsigned int dim = sift.descriptorSize(); + //ULOGGER_DEBUG("row=%d, col=%d, type=%d (float=%d)", d.rows, d.cols, d.type(), CV_32F); + for(int i=0; i(d.ptr(i), d.ptr(i)+dim)); + } + if(imageGrayScale) + { + cvReleaseImage(&imageGrayScale); + } + return descriptors; +} + +////////////////////////// +//LaplacianDescriptor +////////////////////////// +LaplacianDescriptor::LaplacianDescriptor(const ParametersMap & parameters, KeypointDescriptor * childDescriptor) : + KeypointDescriptor(parameters, childDescriptor) +{ + this->parseParameters(parameters); +} + +LaplacianDescriptor::~LaplacianDescriptor() +{ +} + +void LaplacianDescriptor::parseParameters(const ParametersMap & parameters) +{ + // No parameter... + KeypointDescriptor::parseParameters(parameters); +} + +std::list > LaplacianDescriptor::_generateDescriptors(const IplImage * image, const std::list & keypoints) const +{ + ULOGGER_DEBUG(""); + std::list > descriptors; + + //create descriptors... + for(std::list::const_iterator key=keypoints.begin(); key!=keypoints.end(); ++key) + { + std::vector laplacian(1); + laplacian[0] = uSign(key->response); + descriptors.push_back(laplacian); + } + return descriptors; +} + +////////////////////////// +//ColorDescriptor +////////////////////////// +ColorDescriptor::ColorDescriptor(const ParametersMap & parameters, KeypointDescriptor * childDescriptor) : + KeypointDescriptor(parameters, childDescriptor) +{ + this->parseParameters(parameters); +} + +ColorDescriptor::~ColorDescriptor() +{ +} + +void ColorDescriptor::parseParameters(const ParametersMap & parameters) +{ + // No parameter... + KeypointDescriptor::parseParameters(parameters); +} + +std::list > ColorDescriptor::_generateDescriptors(const IplImage * image, const std::list & keypoints) const +{ + ULOGGER_DEBUG(""); + std::list > descriptors; + if(!image) + { + ULOGGER_ERROR("Image is null ?!?"); + return descriptors; + } + + IplImage * imageConverted = 0; + if(image->nChannels != 3 || image->depth != IPL_DEPTH_8U) + { + imageConverted = cvCreateImage(cvSize(image->width,image->height), IPL_DEPTH_8U, 3); + cvCvtColor(image, imageConverted, CV_GRAY2BGR); + } + cv::Mat imgMat; + if(imageConverted) + { + imgMat = cv::Mat(imageConverted); + } + else + { + imgMat = cv::Mat(image); + } + + //create descriptors... + for(std::list::const_iterator key=keypoints.begin(); key!=keypoints.end(); ++key) + { + + int grayMax = -1; // grayValue + int grayMin = -1; // grayValue + float d[6] = {0}; + std::vector RxV; + cv::Point center = cv::Point(cvRound(key->pt.x), cvRound(key->pt.y)); + int R = cvRound(key->size*1.2/9.*2); + this->getCircularROI(R, RxV); + cv::Mat_& img = (cv::Mat_&)imgMat; //3 channel pointer to image + // find the brighter and darker pixels + for( int dy = -R; dy <= R; ++dy ) + { + int Rx = RxV[abs(dy)]; + for( int dx = -Rx; dx <= Rx; ++dx ) + { + if(center.y+dy < img.rows && center.y+dy >= 0 && center.x+dx < img.cols && center.x+dx >= 0) + { + //bgr + uchar b = img(center.y+dy, center.x+dx)[0]; + uchar g = img(center.y+dy, center.x+dx)[1]; + uchar r = img(center.y+dy, center.x+dx)[2]; + int gray = b*0.114 + g*0.587 + r*0.299; + if(grayMax<0 || gray > grayMax) + { + grayMax = gray; + d[0] = b; + d[1] = g; + d[2] = r; + } + if(grayMin<0 || gray < grayMin) + { + grayMin = gray; + d[3] = b; + d[4] = g; + d[5] = r; + } + } + else + { + //ULOGGER_WARN("The keypoint size is outside of the image ranges (x,y)=(%d,%d) radius=%d", center.y+dy, center.x+dx, R); + } + } + } + for(int i=0; i<6; ++i) + { + d[i] /= 255; // Normalize between 0 and 1 + } + descriptors.push_back(std::vector(d, d + sizeof(d) / sizeof(float))); + } + + if(imageConverted) + { + cvReleaseImage(&imageConverted); + } + + return descriptors; +} + +// the function returns x boundary coordinates of +// the circle for each y. RxV[y1] = x1 means that +// when y=y1, -x1 <=x<=x1 is inside the circle +// (from OpenCv doc, C++ Cheatsheet) +void ColorDescriptor::getCircularROI(int R, std::vector & RxV) const +{ + RxV.resize(R+1); + for( int y = 0; y <= R; y++ ) + RxV[y] = cvRound(sqrt(double(R*R - y*y))); +} + +////////////////////////// +//HueDescriptor +////////////////////////// +HueDescriptor::HueDescriptor(const ParametersMap & parameters, KeypointDescriptor * childDescriptor) : + ColorDescriptor(parameters, childDescriptor) +{ + this->parseParameters(parameters); +} + +HueDescriptor::~HueDescriptor() +{ +} + +void HueDescriptor::parseParameters(const ParametersMap & parameters) +{ + // No parameter... + KeypointDescriptor::parseParameters(parameters); +} + +std::list > HueDescriptor::_generateDescriptors(const IplImage * image, const std::list & keypoints) const +{ + ULOGGER_DEBUG(""); + std::list > descriptors; + if(!image) + { + ULOGGER_ERROR("Image is null ?!?"); + return descriptors; + } + + IplImage * imageConverted = 0; + if(image->nChannels != 3 || image->depth != IPL_DEPTH_8U) + { + imageConverted = cvCreateImage(cvSize(image->width,image->height), IPL_DEPTH_8U, 3); + cvCvtColor(image, imageConverted, CV_GRAY2BGR); + } + cv::Mat imgMat; + if(imageConverted) + { + imgMat = cv::Mat(imageConverted); + } + else + { + imgMat = cv::Mat(image); + } + + //create descriptors... + for(std::list::const_iterator key=keypoints.begin(); key!=keypoints.end(); ++key) + { + + int intensityMax = -1; + int intensityMin = -1; + float d[2] = {0}; + std::vector RxV; + cv::Point center = cv::Point(cvRound(key->pt.x), cvRound(key->pt.y)); + int R = cvRound(key->size*1.2/9.*2); + this->getCircularROI(R, RxV); + cv::Mat_& img = (cv::Mat_&)imgMat; //3 channel pointer to image + // find the brighter and darker pixels using the intensity + int dxb=0; + int dyb=0; + int dxd=0; + int dyd=0; + for( int dy = -R; dy <= R; ++dy ) + { + int Rx = RxV[abs(dy)]; + for( int dx = -Rx; dx <= Rx; ++dx ) + { + if(center.y+dy < img.rows && center.y+dy >= 0 && center.x+dx < img.cols && center.x+dx >= 0) + { + //bgr + float b = float(img(center.y+dy, center.x+dx)[0]) / 255.0f; + float g = float(img(center.y+dy, center.x+dx)[1]) / 255.0f; + float r = float(img(center.y+dy, center.x+dx)[2]) / 255.0f; + int intensity = rgb2intensity(r, g, b); + if(intensityMax<0 || intensity > intensityMax) + { + intensityMax = intensity; + dxb = dx; + dyb = dy; + } + if(intensityMin<0 || intensity < intensityMin) + { + intensityMin = intensity; + dxd = dx; + dyd = dy; + } + } + else + { + //ULOGGER_WARN("The keypoint size is outside of the image ranges (x,y)=(%d,%d) radius=%d", center.y+dy, center.x+dx, R); + } + } + } + + // brighter + float b = float(img(center.y+dyb, center.x+dxb)[0]) / 255.0f; + float g = float(img(center.y+dyb, center.x+dxb)[1]) / 255.0f; + float r = float(img(center.y+dyb, center.x+dxb)[2]) / 255.0f; + d[0] = rgb2hue(r, g, b); + + // darker + b = float(img(center.y+dyd, center.x+dxd)[0]) / 255.0f; + g = float(img(center.y+dyd, center.x+dxd)[1]) / 255.0f; + r = float(img(center.y+dyd, center.x+dxd)[2]) / 255.0f; + d[1] = rgb2hue(r, g, b); + + descriptors.push_back(std::vector(d, d + sizeof(d) / sizeof(float))); + } + + if(imageConverted) + { + cvReleaseImage(&imageConverted); + } + + return descriptors; +} + +// assuming that rgb values are normalized [0,1] +float HueDescriptor::rgb2hue(float r, float g, float b) const +{ + double pi = 3.14159265359; + if(b<=g) + { + return acos(((r-g)+(r-b))/(2*sqrt((r-g)*(r-g)+(r-b)*(g-b))))/pi; + } + else + { + return (pi-acos(((r-g)+(r-b))/(2*sqrt((r-g)*(r-g)+(r-b)*(g-b)))))/pi; + } +} + + +} diff --git a/corelib/src/KeypointDetector.cpp b/corelib/src/KeypointDetector.cpp new file mode 100644 index 0000000000..a5a6e717aa --- /dev/null +++ b/corelib/src/KeypointDetector.cpp @@ -0,0 +1,496 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "rtabmap/core/KeypointDetector.h" +#include "VWDictionary.h" +#include "utilite/ULogger.h" +#include "utilite/UTimer.h" +#include "utilite/UStl.h" +#include "rtabmap/core/Parameters.h" +#include "utilite/UConversion.h" +#include +#include +#include + +#define OPENCV_SURF_GPU CV_MAJOR_VERSION >= 2 and CV_MINOR_VERSION >=2 and CV_SUBMINOR_VERSION>=1 + +namespace rtabmap +{ + +KeypointDetector::KeypointDetector(const ParametersMap & parameters) : + _wordsPerImageTarget(Parameters::defaultKpWordsPerImage()), + _usingAdaptiveResponseThr(Parameters::defaultKpUsingAdaptiveResponseThr()), + _adaptiveResponseThr(1), + _roiRatios(std::vector(4, 0.0f)) +{ + this->setRoi(Parameters::defaultKpRoiRatios()); + this->parseParameters(parameters); +} + +void KeypointDetector::parseParameters(const ParametersMap & parameters) +{ + ParametersMap::const_iterator iter; + if((iter=parameters.find(Parameters::kKpWordsPerImage())) != parameters.end()) + { + _wordsPerImageTarget = std::atoi((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kKpUsingAdaptiveResponseThr())) != parameters.end()) + { + _usingAdaptiveResponseThr = uStr2Bool((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kKpRoiRatios())) != parameters.end()) + { + this->setRoi((*iter).second); + } +} + +std::list KeypointDetector::generateKeypoints(const IplImage * image) +{ + ULOGGER_DEBUG(""); + std::list keypoints; + if(image) + { + UTimer timer; + timer.start(); + + cv::Rect roi = computeRoi(image); + + // Get keypoints + keypoints = this->_generateKeypoints(image, roi); + ULOGGER_DEBUG("Keypoints extraction time = %f s, keypoints extracted = %d", timer.ticks(), keypoints.size()); + + //clip the number of words... to _wordsPerImageTarget + // Variable hessian threshold + if(_wordsPerImageTarget > 0) + { + ULOGGER_DEBUG("_adaptiveResponseThr=%f", _adaptiveResponseThr); + if(keypoints.size() > 0) + { + if(keypoints.size() > _wordsPerImageTarget) + { + _adaptiveResponseThr *= 1+((float(keypoints.size())/float(_wordsPerImageTarget)-1)/1000); + } + else if(keypoints.size() < _wordsPerImageTarget) + { + _adaptiveResponseThr *= 1-((1-float(keypoints.size())/float(_wordsPerImageTarget))/1); + } + // 10% margin... + if(keypoints.size() > 1.1 * _wordsPerImageTarget) + { + ULOGGER_DEBUG("too much words (%d), removing words under the new hessian threshold", keypoints.size()); + // Remove words under the new hessian threshold + + // Sort words by hessian + std::multimap::iterator> hessianMap; // + for(std::list::iterator itKey = keypoints.begin(); itKey != keypoints.end(); ++itKey) + { + //Keep track of the data, to be easier to manage the data in the next step + hessianMap.insert(std::pair::iterator>(fabs(itKey->response), itKey)); + } + + // Remove them from the signature + int removed = 0; + unsigned int stopIndex = hessianMap.size()-_wordsPerImageTarget; + std::multimap::iterator>::iterator iter = hessianMap.begin(); + for(unsigned int k=0; k < stopIndex && iter!=hessianMap.end(); ++k, ++iter) + { + keypoints.erase(iter->second); + ++removed; + } + if(iter->first!=0) + { + _adaptiveResponseThr = iter->first; + } + ULOGGER_DEBUG("%d keypoints removed, (kept %d)", removed, keypoints.size()); + } + } + else + { + _adaptiveResponseThr /= 2; + } + + if(_adaptiveResponseThr < this->getMinimumResponseThr()) + { + _adaptiveResponseThr = this->getMinimumResponseThr(); + } + + ULOGGER_DEBUG("new _adaptiveResponseThr=%f", _adaptiveResponseThr); + ULOGGER_DEBUG("adjusting hessian threshold time = %f s", timer.ticks()); + } + + // Adjust keypoint position to raw image + for(std::list::iterator iter=keypoints.begin(); iter!=keypoints.end(); ++iter) + { + iter->pt.x += roi.x; + iter->pt.y += roi.y; + } + } + else + { + ULOGGER_ERROR("Image is null!"); + } + return keypoints; +} + +void KeypointDetector::setRoi(const std::string & roi) +{ + std::list strValues = uSplit(roi, ' '); + if(strValues.size() != 4) + { + ULOGGER_ERROR("The number of values must be 4 (roi=\"%s\")", roi.c_str()); + } + else + { + std::vector tmpValues(4); + unsigned int i=0; + for(std::list::iterator iter = strValues.begin(); iter!=strValues.end(); ++iter) + { + tmpValues[i] = std::atof((*iter).c_str()); + ++i; + } + + if(tmpValues[0] >= 0 && tmpValues[0] < 1 && tmpValues[0] < 1.0f-tmpValues[1] && + tmpValues[1] >= 0 && tmpValues[1] < 1 && tmpValues[1] < 1.0f-tmpValues[0] && + tmpValues[2] >= 0 && tmpValues[2] < 1 && tmpValues[2] < 1.0f-tmpValues[3] && + tmpValues[3] >= 0 && tmpValues[3] < 1 && tmpValues[3] < 1.0f-tmpValues[2]) + { + _roiRatios = tmpValues; + } + else + { + ULOGGER_ERROR("The roi ratios are not valid (roi=\"%s\")", roi.c_str()); + } + } +} + +cv::Rect KeypointDetector::computeRoi(const IplImage * image) const +{ + if(image && _roiRatios.size() == 4) + { + cv::Rect roi(0, 0, image->width, image->height); + UDEBUG("roi ratios = %f, %f, %f, %f", _roiRatios[0],_roiRatios[1],_roiRatios[2],_roiRatios[3]); + UDEBUG("roi = %d, %d, %d, %d", roi.x, roi.y, roi.width, roi.height); + + float width = image->width; + float height = image->height; + //left roi + if(_roiRatios[0] > 0 && _roiRatios[0] < 1 - _roiRatios[1]) + { + roi.x = width * _roiRatios[0]; + } + + //right roi + roi.width = width - roi.x; + if(_roiRatios[1] > 0 && _roiRatios[1] < 1 - _roiRatios[0]) + { + roi.width -= width * _roiRatios[1]; + } + + //top roi + if(_roiRatios[2] > 0 && _roiRatios[2] < 1 - _roiRatios[3]) + { + roi.y = height * _roiRatios[2]; + } + + //bottom roi + roi.height = height - roi.y; + if(_roiRatios[3] > 0 && _roiRatios[3] < 1 - _roiRatios[2]) + { + roi.height -= height * _roiRatios[3]; + } + UDEBUG("roi = %d, %d, %d, %d", roi.x, roi.y, roi.width, roi.height); + + return roi; + } + else + { + UERROR("Image is null or _roiRatios(=%d) != 4", _roiRatios.size()); + return cv::Rect(); + } +} + + +////////////////////////// +//SURFDetector +////////////////////////// +SURFDetector::SURFDetector(const ParametersMap & parameters) : + KeypointDetector(parameters) +{ + _surf.hessianThreshold = Parameters::defaultSURFHessianThreshold(); + _surf.extended = Parameters::defaultSURFExtended(); + _surf.nOctaveLayers = Parameters::defaultSURFOctaveLayers(); + _surf.nOctaves = Parameters::defaultSURFOctaves(); + _gpuVersion = Parameters::defaultSURFGpuVersion(); + _upright = Parameters::defaultSURFUpright(); + this->parseParameters(parameters); + this->setAdaptiveResponseThr(_surf.hessianThreshold); +} + +SURFDetector::~SURFDetector() +{ +} + +void SURFDetector::parseParameters(const ParametersMap & parameters) +{ + ParametersMap::const_iterator iter; + if((iter=parameters.find(Parameters::kSURFExtended())) != parameters.end()) + { + _surf.extended = uStr2Bool((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kSURFHessianThreshold())) != parameters.end()) + { + _surf.hessianThreshold = std::atof((*iter).second.c_str()); + this->setAdaptiveResponseThr(_surf.hessianThreshold); + } + if((iter=parameters.find(Parameters::kSURFOctaveLayers())) != parameters.end()) + { + _surf.nOctaveLayers = std::atoi((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kSURFOctaves())) != parameters.end()) + { + _surf.nOctaves = std::atoi((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kSURFOctaves())) != parameters.end()) + { + _surf.nOctaves = std::atoi((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kSURFGpuVersion())) != parameters.end()) + { + _gpuVersion = uStr2Bool((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kSURFUpright())) != parameters.end()) + { + _upright = uStr2Bool((*iter).second.c_str()); + } + KeypointDetector::parseParameters(parameters); +} + +std::list SURFDetector::_generateKeypoints(const IplImage * image, const cv::Rect & roi) const +{ + ULOGGER_DEBUG(""); + std::list keypoints; + if(!image) + { + ULOGGER_ERROR("Image is null ?!?"); + return keypoints; + } + // SURF support only grayscale images + IplImage * imageGrayScale = 0; + if(image->nChannels != 1 || image->depth != IPL_DEPTH_8U) + { + imageGrayScale = cvCreateImage(cvSize(image->width,image->height), IPL_DEPTH_8U, 1); + cvCvtColor(image, imageGrayScale, CV_BGR2GRAY); + } + cv::Mat img; + if(imageGrayScale) + { + img = cv::Mat(imageGrayScale); + } + else + { + img = cv::Mat(image); + } + + cv::SURF surf = _surf; + if(this->isUsingAdaptiveResponseThr()) + { + surf.hessianThreshold = this->getAdaptiveResponseThr(); // use the adaptive threshold + } + + cv::Mat imgRoi(img, roi); + std::vector k; +#if OPENCV_SURF_GPU + if(_gpuVersion ) + { + cv::gpu::GpuMat imgGpu(imgRoi); + cv::gpu::GpuMat keypointsGpu; + cv::gpu::SURF_GPU surfGpu(surf.hessianThreshold, surf.nOctaves, surf.nOctaveLayers, surf.extended, 0.01f, _upright); + surfGpu(imgGpu, cv::gpu::GpuMat(), keypointsGpu); + surfGpu.downloadKeypoints(keypointsGpu, k); + } + else + { + surf(imgRoi, cv::Mat(), k); // Opencv surf keypoints + } +#else + surf(imgRoi, cv::Mat(), k); // Opencv surf keypoints +#endif + keypoints = uVectorToList(k); + + + if(imageGrayScale) + { + cvReleaseImage(&imageGrayScale); + } + return keypoints; +} + +////////////////////////// +//SIFTDetector +////////////////////////// +SIFTDetector::SIFTDetector(const ParametersMap & parameters) : + KeypointDetector(parameters) +{ + _detectorParams.threshold = Parameters::defaultSIFTThreshold(); + _detectorParams.edgeThreshold = Parameters::defaultSIFTEdgeThreshold(); + this->parseParameters(parameters); + this->setAdaptiveResponseThr(_detectorParams.threshold); +} + +SIFTDetector::~SIFTDetector() +{ +} + +void SIFTDetector::parseParameters(const ParametersMap & parameters) +{ + ParametersMap::const_iterator iter; + if((iter=parameters.find(Parameters::kSIFTThreshold())) != parameters.end()) + { + _detectorParams.threshold = std::atof((*iter).second.c_str()); + this->setAdaptiveResponseThr(_detectorParams.threshold); + } + if((iter=parameters.find(Parameters::kSIFTEdgeThreshold())) != parameters.end()) + { + _detectorParams.edgeThreshold = std::atof((*iter).second.c_str()); + } + KeypointDetector::parseParameters(parameters); +} + +std::list SIFTDetector::_generateKeypoints(const IplImage * image, const cv::Rect & roi) const +{ + ULOGGER_DEBUG(""); + std::list keypoints; + if(!image) + { + ULOGGER_ERROR("Image is null ?!?"); + return keypoints; + } + // SURF support only grayscale images + IplImage * imageGrayScale = 0; + if(image->nChannels != 1 || image->depth != IPL_DEPTH_8U) + { + imageGrayScale = cvCreateImage(cvSize(image->width,image->height), IPL_DEPTH_8U, 1); + cvCvtColor(image, imageGrayScale, CV_BGR2GRAY); + } + cv::Mat img; + if(imageGrayScale) + { + img = cv::Mat(imageGrayScale); + } + else + { + img = cv::Mat(image); + } + + cv::SIFT::DetectorParams detectorParam = _detectorParams; + if(this->isUsingAdaptiveResponseThr()) + { + detectorParam.threshold = this->getAdaptiveResponseThr(); // use the adaptive threshold + } + cv::Mat mask; + cv::SIFT sift(_commonParams, detectorParam); + + cv::Mat imgRoi(img, roi); + std::vector k; + sift(imgRoi, mask, k); // Opencv surf keypoints + keypoints = uVectorToList(k); + if(imageGrayScale) + { + cvReleaseImage(&imageGrayScale); + } + return keypoints; +} + + +////////////////////////// +//StarDetector +////////////////////////// +StarDetector::StarDetector(const ParametersMap & parameters) : + KeypointDetector(parameters) +{ + _star.lineThresholdBinarized = Parameters::defaultStarLineThresholdBinarized(); + _star.lineThresholdProjected = Parameters::defaultStarLineThresholdProjected(); + _star.maxSize = Parameters::defaultStarMaxSize(); + _star.responseThreshold = Parameters::defaultStarResponseThreshold(); + _star.suppressNonmaxSize = Parameters::defaultStarSuppressNonmaxSize(); + this->parseParameters(parameters); + this->setAdaptiveResponseThr(_star.responseThreshold); +} + +StarDetector::~StarDetector() +{ +} + +void StarDetector::parseParameters(const ParametersMap & parameters) +{ + ULOGGER_WARN("The StarDetector parameters can't be changed on ROS (this is an issue with the default (and too old) opencv revision used in ROS)"); + ParametersMap::const_iterator iter; + if((iter=parameters.find(Parameters::kStarLineThresholdBinarized())) != parameters.end()) + { + _star.lineThresholdBinarized = std::atoi((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kStarLineThresholdProjected())) != parameters.end()) + { + _star.lineThresholdProjected = std::atoi((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kStarMaxSize())) != parameters.end()) + { + _star.maxSize = std::atoi((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kStarResponseThreshold())) != parameters.end()) + { + _star.responseThreshold = int(std::atof((*iter).second.c_str())); + this->setAdaptiveResponseThr(_star.responseThreshold); + } + if((iter=parameters.find(Parameters::kStarSuppressNonmaxSize())) != parameters.end()) + { + _star.suppressNonmaxSize = std::atoi((*iter).second.c_str()); + } + KeypointDetector::parseParameters(parameters); +} + +std::list StarDetector::_generateKeypoints(const IplImage * image, const cv::Rect & roi) const +{ + ULOGGER_DEBUG(""); + std::list keypoints; + if(!image) + { + ULOGGER_ERROR("Image is null ?!?"); + return keypoints; + } + + cv::Mat img(image); + cv::Mat mask; + // TODO More testing needed with the star detector, NN search distance must be changed to 0.8 + //find keypoints with the star detector + cv::StarDetector star = _star; + if(this->isUsingAdaptiveResponseThr()) + { + star.responseThreshold = this->getAdaptiveResponseThr(); // use the adaptive threshold + } + + // Get keypoints with the star detector + cv::Mat imgRoi(img, roi); + std::vector k; + star(imgRoi, k); + keypoints = uVectorToList(k); + return keypoints; +} + +} diff --git a/corelib/src/KeypointMemory.cpp b/corelib/src/KeypointMemory.cpp new file mode 100644 index 0000000000..5093f46170 --- /dev/null +++ b/corelib/src/KeypointMemory.cpp @@ -0,0 +1,1219 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "KeypointMemory.h" +#include "VWDictionary.h" +#include "Signature.h" +#include "rtabmap/core/DBDriver.h" +#include "utilite/UtiLite.h" +#include "rtabmap/core/Parameters.h" +#include "rtabmap/core/SMState.h" +#include "rtabmap/core/KeypointDetector.h" +#include "rtabmap/core/KeypointDescriptor.h" +#include "rtabmap/core/RtabmapEvent.h" +#include "NearestNeighbor.h" +#include "VerifyHypotheses.h" +#include "utilite/UStl.h" +#include +#include + +namespace rtabmap { + +KeypointMemory::KeypointMemory(const ParametersMap & parameters) : + Memory(parameters), + _keypointDetector(0), + _keypointDescriptor(0), + _reactivatedWordsComparedToNewWords(Parameters::defaultKpReactivatedWordsComparedToNewWords()), + _badSignRatio(Parameters::defaultKpBadSignRatio()), + _tfIdfLikelihoodUsed(Parameters::defaultKpTfIdfLikelihoodUsed()), + _parallelized(Parameters::defaultKpParallelized()), + _tfIdfNormalized(Parameters::defaultKpTfIdfNormalized()) +{ + _vwd = new VWDictionary(parameters); + this->parseParameters(parameters); +} + +KeypointMemory::~KeypointMemory() +{ + ULOGGER_DEBUG(""); + if(this->memoryChanged()) + { + this->clear(); + } + if(_keypointDetector) + { + delete _keypointDetector; + } + if(_keypointDescriptor) + { + delete _keypointDescriptor; + } + if(_vwd) + { + delete _vwd; + } +} + +void KeypointMemory::parseParameters(const ParametersMap & parameters) +{ + ParametersMap::const_iterator iter; + + if(_vwd) + { + _vwd->parseParameters(parameters); + } + + if((iter=parameters.find(Parameters::kKpReactivatedWordsComparedToNewWords())) != parameters.end()) + { + _reactivatedWordsComparedToNewWords = uStr2Bool((*iter).second.c_str()); + } + + if((iter=parameters.find(Parameters::kKpTfIdfLikelihoodUsed())) != parameters.end()) + { + _tfIdfLikelihoodUsed = uStr2Bool((*iter).second.c_str()); + } + + if((iter=parameters.find(Parameters::kKpParallelized())) != parameters.end()) + { + _parallelized = uStr2Bool((*iter).second.c_str()); + } + + if((iter=parameters.find(Parameters::kKpTfIdfNormalized())) != parameters.end()) + { + _tfIdfNormalized = uStr2Bool((*iter).second.c_str()); + } + + if((iter=parameters.find(Parameters::kKpBadSignRatio())) != parameters.end()) + { + _badSignRatio = std::atof((*iter).second.c_str()); + } + + //Keypoint detector + DetectorStrategy detectorStrategy = kDetectorUndef; + if((iter=parameters.find(Parameters::kKpDetectorStrategy())) != parameters.end()) + { + detectorStrategy = (DetectorStrategy)std::atoi((*iter).second.c_str()); + } + DetectorStrategy currentDetectorStrategy = this->detectorStrategy(); + if(!_keypointDetector || ( detectorStrategy!=kDetectorUndef && (detectorStrategy != currentDetectorStrategy) ) ) + { + ULOGGER_DEBUG("new detector strategy %d", int(detectorStrategy)); + if(_keypointDetector) + { + delete _keypointDetector; + _keypointDetector = 0; + } + switch(detectorStrategy) + { + case kDetectorStar: + _keypointDetector = new StarDetector(parameters); + break; + case kDetectorSift: + _keypointDetector = new SIFTDetector(parameters); + break; + case kDetectorSurf: + default: + _keypointDetector = new SURFDetector(parameters); + break; + } + } + else if(_keypointDetector) + { + _keypointDetector->parseParameters(parameters); + } + + //Keypoint descriptor + DescriptorStrategy descriptorStrategy = kDescriptorUndef; + if((iter=parameters.find(Parameters::kKpDescriptorStrategy())) != parameters.end()) + { + descriptorStrategy = (DescriptorStrategy)std::atoi((*iter).second.c_str()); + } + if(!_keypointDescriptor || descriptorStrategy!=kDescriptorUndef) + { + ULOGGER_DEBUG("new descriptor strategy %d", int(descriptorStrategy)); + if(_keypointDescriptor) + { + delete _keypointDescriptor; + _keypointDescriptor = 0; + } + switch(descriptorStrategy) + { + case kDescriptorColorSurf: + // see decorator pattern... + _keypointDescriptor = new ColorDescriptor(parameters, new SURFDescriptor(parameters)); + break; + case kDescriptorLaplacianSurf: + // see decorator pattern... + _keypointDescriptor = new LaplacianDescriptor(parameters, new SURFDescriptor(parameters)); + break; + case kDescriptorSift: + _keypointDescriptor = new SIFTDescriptor(parameters); + break; + case kDescriptorHueSurf: + // see decorator pattern... + _keypointDescriptor = new HueDescriptor(parameters, new SURFDescriptor(parameters)); + break; + case kDescriptorSurf: + default: + _keypointDescriptor = new SURFDescriptor(parameters); + break; + } + } + else if(_keypointDescriptor) + { + _keypointDescriptor->parseParameters(parameters); + } + + Memory::parseParameters(parameters); +} + +KeypointMemory::DetectorStrategy KeypointMemory::detectorStrategy() const +{ + DetectorStrategy strategy = kDetectorUndef; + StarDetector * star = dynamic_cast(_keypointDetector); + SURFDetector * surf = dynamic_cast(_keypointDetector); + if(star) + { + strategy = kDetectorStar; + } + else if(surf) + { + strategy = kDetectorSurf; + } + return strategy; +} + +bool KeypointMemory::init(const std::string & dbDriverName, const std::string & dbUrl, bool dbOverwritten, const ParametersMap & parameters) +{ + ULOGGER_DEBUG("KeypointMemory::init()"); + // This will open a connection to the database, + // this calls also clear() + bool success = Memory::init(dbDriverName, dbUrl, dbOverwritten, parameters); + + // Now load the dictionary if we have a connection + if(_vwd && _dbDriver && _dbDriver->isConnected()) + { + UEventsManager::post(new RtabmapEventInit(std::string("Loading dictionary..."))); + _dbDriver->load(_vwd); + ULOGGER_DEBUG("%d words loaded!", _vwd->getVisualWords().size()); + UEventsManager::post(new RtabmapEventInit(std::string("Loading dictionary, done! (") + uNumber2str(int(_vwd->getVisualWords().size())) + " loaded)")); + } + + // Enable loaded signatures + KeypointSignature * ss; + const std::map & signatures = this->getSignatures(); + for(std::map::const_iterator i=signatures.begin(); i!=signatures.end(); ++i) + { + ss = dynamic_cast(this->_getSignature(i->first)); + if(ss) + { + ss->setEnabled(true); + } + } + + if(_vwd) + { + ULOGGER_DEBUG("Total word reference added = %d", _vwd->getTotalActiveReferences()); + } + + //if(this->isCommonSignatureUsed()) + //{ + // Memory::updateCommonSignature(); // With words loaded, update the virtual place + //} + return success; +} + +// TODO : Use only the parent method (in Memory) +// 1- Put "setEnabled" in the abstract Signature class, so this method is accessible from Memory +void KeypointMemory::addSignatureToStm(Signature * signature, const std::list > & actions) +{ + ULOGGER_DEBUG(""); + Memory::addSignatureToStm(signature, actions); + + UTimer timer; + KeypointSignature * ss = dynamic_cast(signature); + if(ss) + { + if(_vwd) + { + ULOGGER_DEBUG("%d words ref for the signature %d", ss->getWords().size(), ss->id()); + } + if(ss->getWords().size()) + { + ss->setEnabled(true); + } + } + UDEBUG("time = %fs", timer.ticks()); +} + +void KeypointMemory::clear() +{ + ULOGGER_DEBUG(""); + + // Save some SURF stats to the db (Must be before Memory::clear()) + if(_dbDriver) + { + int size = 0; + if(_vwd) + { + size = _vwd->getVisualWords().size(); + } + + _dbDriver->addStatisticsAfterRunSurf(size); + } + + Memory::clear(); + + ULOGGER_DEBUG(""); + + if(_dbDriver) + { + _dbDriver->kill(); + cleanUnusedWords(); + _dbDriver->emptyTrashes(); + + //if(_wordRefsToChange.size()) + //{ + // ULOGGER_DEBUG("Changing old word references (%d) in the database...", _wordRefsToChange.size()); + // _dbDriver->beginTransaction(); + // Change all words reference for + // all signatures with the old word to the active one... + //_dbDriver->changeWordsRef(_wordRefsToChange); + //remove old values + _dbDriver->deleteUnreferencedWords(); + // _dbDriver->commit(); + //} + ULOGGER_DEBUG(""); + + _dbDriver->start(); + } + else + { + cleanUnusedWords(); + } + + _commonWords.clear(); +} + +void KeypointMemory::preUpdate() +{ + Memory::preUpdate(); + if(_vwd && !_parallelized) + { + //When parallelized, it is done in CreateSignature + this->cleanUnusedWords(); + _vwd->update(); + } +} + + +// TODO Really useful? +/*void KeypointMemory::postUpdate() +{ + ULOGGER_DEBUG(""); + // Detect if the last signature is a bad one. If the signature has less than 15% of + // the average words/signature. + KeypointSignature * ss = dynamic_cast(this->_getLastSignature()); + + float ratio = 0; + if(ss) + { + ratio = float(uUniqueKeys(ss->getWords()).size()) / float(ss->getWords().size()); + } + + int nbCommonWords = 0; + ULOGGER_DEBUG("_workingMem.size() = %d, _stMem.size()=%d", _workingMem.size(), _stMem.size()); + int treeSize= _workingMem.size() + _stMem.size();//Don't count the virtual place + if(treeSize > 0) + { + nbCommonWords = _vwd->getTotalActiveReferences() / treeSize; + } + + ULOGGER_DEBUG("ratio=%f, treeSize=%d, nbCommonWords=%d", ratio, treeSize, nbCommonWords); + + if(//(ratio < _badSignRatio) || + (nbCommonWords && ss && ss->getWords().size() < _badSignRatio * nbCommonWords)) + { + ULOGGER_WARN("id %d is a bad signature", ss->id()); + this->disableWordsRef(ss->id()); + ss->removeAllWords(); + } +}*/ + +// NON class method! only used in merge() +std::multimap getMostDescriptiveWords(const std::multimap & words, int max, const std::set & ignoredIds) +{ + std::multimap mostDescriptiveWords; + if(max > 0) + { + std::multimap > responseWordMap; + //get all descriptors from features not found in the two signatures + for(std::multimap::const_iterator itKey = words.begin(); itKey != words.end(); ++itKey) + { + if(ignoredIds.find(itKey->first) == ignoredIds.end()) + { + responseWordMap.insert(std::pair >(itKey->second.response, std::pair(itKey->first, &(itKey->second)))); + } + } + int endIndex = 0; + if(responseWordMap.size() > (unsigned int)max) + { + endIndex = responseWordMap.size() - max; + } + + //add them + int i=0; + std::multimap >::reverse_iterator iter = responseWordMap.rbegin(); + for(; iter != responseWordMap.rend(); ++iter, ++i) + { + if(i>=max) + { + break; + } + mostDescriptiveWords.insert(std::pair(iter->second.first, *(iter->second.second))); + } + } + return mostDescriptiveWords; +} + +void KeypointMemory::merge(const Signature * from, Signature * to, MergingStrategy s) +{ + // The signatures must be KeypointSignature + const KeypointSignature * sFrom = dynamic_cast(from); + KeypointSignature * sTo = dynamic_cast(to); + UTimer timer; + timer.start(); + if(sFrom && sTo) + { + const std::multimap & wordsA = sFrom->getWords(); + if(wordsA.size()) + { + UTimer timer2; + const std::multimap & wordsB = sTo->getWords(); + int maxWords = wordsA.size()>wordsB.size()?wordsA.size():wordsB.size(); + //if(_keypointDescriptor) + //{ + // maxWords = _keypointDetector->getWordsPerImageTarget(); + //} + + std::multimap newWords; + if(s == kFullMerging) + { + // add features contained in each signatures + std::list > pairs; + std::list pairsId; + std::set pairsIdSet; + VerifyHypothesesEpipolarGeo::findPairsDirect(wordsA, wordsB, pairs, pairsId); + std::list >::iterator kpIt = pairs.begin(); + std::list::iterator idIt = pairsId.begin(); + for(; kpIt != pairs.end() && idIt != pairsId.end(); ++kpIt, ++idIt) + { + newWords.insert(newWords.end(), std::pair(*idIt, kpIt->second)); + pairsIdSet.insert(pairsIdSet.end(), *idIt); + } + ULOGGER_DEBUG("newWords.size() = %d, time add pairs = %fs", newWords.size(), timer2.ticks()); + + // fill the merged target signature with the farthest features from each signatures + int dif = maxWords - (int)newWords.size(); + if(dif>0) + { + ////////// + // Add words with the highest response from the two signatures + ////////// + std::multimap allWords = wordsA; + allWords.insert(wordsB.begin(), wordsB.end()); + allWords = getMostDescriptiveWords(allWords, dif, pairsIdSet); + newWords.insert(allWords.begin(), allWords.end()); + ////////// + + + ////////// + // Add words by using the descriptor distance, TIME COMSUMMING! though more precise... + ////////// + /*std::list > descriptors; + //get all descriptors from features not found in the two signatures + // for A + for(std::multimap::const_iterator itKey = wordsA.begin(); itKey != wordsA.end(); ++itKey) + { + if(pairsIdSet.find(itKey->first) == pairsIdSet.end()) + { + descriptors.push_back(std::pair(_vwd->getWord(itKey->first), &(itKey->second))); + } + } + // for B + for(std::multimap::const_iterator itKey = wordsB.begin(); itKey != wordsB.end(); ++itKey) + { + if(pairsIdSet.find(itKey->first) == pairsIdSet.end()) + { + descriptors.push_back(std::pair(_vwd->getWord(itKey->first), &(itKey->second))); + } + } + + ULOGGER_DEBUG("descriptors.size()=%d, wordsA.size()=%d, wordsB.size()=%d", descriptors.size(), wordsA.size(), wordsB.size()); + if(descriptors.size() > (unsigned int)dif) + { + FlannKdTreeNN nn; + nn.setStrategy(FlannKdTreeNN::kKDTree); + int k=2; + cv::Mat results(descriptors.size(), k, CV_32SC1); // results index + cv::Mat dists; + if(nn.isDist64F()) + { + dists = cv::Mat(descriptors.size(), k, CV_64FC1); // Distance results are CV_64FC1; + } + else + { + dists = cv::Mat(descriptors.size(), k, CV_32FC1); // Distance results are CV_32FC1 + } + cv::Mat data(descriptors.size(), descriptors.begin()->first->getDim(), CV_32F); // SURF descriptors are CV_32F + + ULOGGER_DEBUG("time prepare nn = %fs", timer2.ticks()); + nn.search(data, data, results, dists, k, descriptors.begin()->first->getDim()); + ULOGGER_DEBUG("time nn = %fs", timer2.ticks()); + + //add keypoint which are farthest of their second neighbor + std::list >::iterator iter = descriptors.begin(); + if(nn.isDist64F()) + { + std::multimap > fartherWords; + for(unsigned int i=0; i= (unsigned int)dif && dists.at(i,1) > fartherWords.begin()->first) + { + fartherWords.erase(fartherWords.begin()); + fartherWords.insert(std::pair >(dists.at(i,1), std::pair(iter->first->id(), iter->second))); + } + else if(fartherWords.size() < (unsigned int)dif) + { + fartherWords.insert(std::pair >(dists.at(i,1), std::pair(iter->first->id(), iter->second))); + } + + ++iter; + } + ULOGGER_DEBUG("fartherWords.size()=%d",fartherWords.size()); + std::map >::iterator iterfw = fartherWords.begin(); + for(; iterfw != fartherWords.end(); ++iterfw) + { + //ULOGGER_DEBUG("adding words %d", iterfw->second.first); + newWords.insert(std::pair(iterfw->second.first, *(iterfw->second.second))); + } + } + else + { + std::multimap > fartherWords; + for(unsigned int i=0; i= (unsigned int)dif && dists.at(i,1) > fartherWords.begin()->first) + { + fartherWords.erase(fartherWords.begin()); + fartherWords.insert(std::pair >(dists.at(i,1), std::pair(iter->first->id(), iter->second))); + } + else if(fartherWords.size() < (unsigned int)dif) + { + fartherWords.insert(std::pair >(dists.at(i,1), std::pair(iter->first->id(), iter->second))); + } + ++iter; + } + ULOGGER_DEBUG("fartherWords.size()=%d",fartherWords.size()); + std::map >::iterator iterfw = fartherWords.begin(); + for(; iterfw != fartherWords.end(); ++iterfw) + { + //ULOGGER_DEBUG("adding words %d", iterfw->second.first); + newWords.insert(std::pair(iterfw->second.first, *(iterfw->second.second))); + } + } + } + else + { + //add all + std::list >::iterator iter = descriptors.begin(); + for(; iter != descriptors.end(); ++iter) + { + //ULOGGER_DEBUG("adding words %d", iter->first->id()); + newWords.insert(std::pair(iter->first->id(), *(iter->second))); + } + }*/ + ////////// + } + ULOGGER_DEBUG("time add to newWords = %fs", timer2.ticks()); + ULOGGER_DEBUG("maxWords=%d, pairsCount=%d, dif = %d, newWords.size()=%d, _vwd->getUnusedWordsSize()=%d", maxWords, pairs.size(), dif, newWords.size(), _vwd->getUnusedWordsSize()); + this->disableWordsRef(sTo->id()); + sTo->setWords(newWords); + std::list id; + id.push_back(sTo->id()); + this->enableWordsRef(id); + } + else if(s == kUseOnlyFromMerging) + { + this->disableWordsRef(sTo->id()); + if(wordsA.size() >= (unsigned int)maxWords) + { + sTo->setWords(wordsA); + } + else + { + std::list > pairs; + std::list pairsId; + VerifyHypothesesEpipolarGeo::findPairsDirect(wordsA, wordsB, pairs, pairsId); + std::set ignoredIds(pairsId.begin(), pairsId.end()); + int dif = maxWords - (int)wordsA.size(); + newWords = getMostDescriptiveWords(wordsB, dif, ignoredIds); + newWords.insert(wordsA.begin(), wordsA.end()); + sTo->setWords(newWords); + } + std::list id; + id.push_back(sTo->id()); + this->enableWordsRef(id); + // Set old image to new merged signature + sTo->setImage(sFrom->getImage()); + } + else if(s == kUseOnlyDestMerging) + { + if(wordsB.size() >= (unsigned int)maxWords) + { + // do nothing... already "merged" + } + else + { + this->disableWordsRef(sTo->id()); + if(wordsB.size() == 0) + { + ULOGGER_WARN("The merged signature is empty!"); + } + std::list > pairs; + std::list pairsId; + VerifyHypothesesEpipolarGeo::findPairsDirect(wordsA, wordsB, pairs, pairsId); + std::set ignoredIds(pairsId.begin(), pairsId.end()); + int dif = maxWords - (int)wordsB.size(); + newWords = getMostDescriptiveWords(wordsA, dif, ignoredIds); + newWords.insert(wordsB.begin(), wordsB.end()); + sTo->setWords(newWords); + std::list id; + id.push_back(sTo->id()); + this->enableWordsRef(id); + } + + } + } + } + else + { + ULOGGER_ERROR("Can't merge the signatures because there are not same type."); + } + ULOGGER_DEBUG("Merging time = %fs", timer.ticks()); +} + +std::map KeypointMemory::computeLikelihood(const Signature * signature, const std::set & signatureIds) const +{ + //return Memory::computeLikelihood(signature, signatureIds); + + // TODO cleanup , old way... + if(_tfIdfLikelihoodUsed) + { + UTimer timer; + timer.start(); + std::map likelihood; + std::map calculatedWordsRatio; + + const KeypointSignature * newSurf = dynamic_cast(signature); + if(!newSurf) + { + ULOGGER_ERROR("The signature is not a KeypointSignature"); + return likelihood; // Must be a KeypointSignature * + } + + if(signatureIds.size() == 0) + { + const std::map & wm = this->getWorkingMem(); + for(std::map::const_iterator iter = wm.begin(); iter!=wm.end(); ++iter) + { + likelihood.insert(likelihood.end(), std::pair(iter->first, 0)); + if(_tfIdfNormalized) + { + const KeypointSignature * s = dynamic_cast(this->getSignature(iter->first)); + float wordsCountRatio = -1; // default invalid + if(s) + { + if(s->getWords().size() > newSurf->getWords().size()) + { + wordsCountRatio = float(newSurf->getWords().size()) / float(s->getWords().size()); + } + else if(newSurf->getWords().size()) + { + wordsCountRatio = float(s->getWords().size()) / float(newSurf->getWords().size()); + } + calculatedWordsRatio.insert(std::pair(iter->first, wordsCountRatio)); + } + else + { + calculatedWordsRatio.insert(std::pair(iter->first, wordsCountRatio)); + } + } + } + } + else + { + for(std::set::const_iterator i=signatureIds.begin(); i != signatureIds.end(); ++i) + { + likelihood.insert(likelihood.end(), std::pair(*i, 0)); + if(_tfIdfNormalized) + { + const KeypointSignature * s = dynamic_cast(this->getSignature(*i)); + float wordsCountRatio = -1; // default invalid + if(s) + { + if(s->getWords().size() > newSurf->getWords().size()) + { + wordsCountRatio = float(newSurf->getWords().size()) / float(s->getWords().size()); + } + else if(newSurf->getWords().size()) + { + wordsCountRatio = float(s->getWords().size()) / float(newSurf->getWords().size()); + } + calculatedWordsRatio.insert(std::pair(*i, wordsCountRatio)); + } + else + { + calculatedWordsRatio.insert(std::pair(*i, wordsCountRatio)); + } + } + } + } + + const std::list & wordIds = uUniqueKeys(newSurf->getWords()); + + float nwi; // nwi is the number of a specific word referenced by a place + float ni; // ni is the total of words referenced by a place + float nw; // nw is the number of places referenced by a specific word + float N; // N is the total number of places + + float logNnw; + const VisualWord * vw; + float normalizationRatio; + + N = likelihood.size(); + + if(N) + { + ULOGGER_DEBUG("processing... "); + // Pour chaque mot dans la signature SURF + for(std::list::const_iterator i=wordIds.begin(); i!=wordIds.end(); ++i) + { + // "Inverted index" - Pour chaque endroit contenu dans chaque mot + vw = _vwd->getWord(*i); + if(vw) + { + const std::map & refs = vw->getReferences(); + nw = refs.size(); + if(nw) + { + logNnw = log10(N/nw); + if(logNnw) + { + for(std::map::const_iterator j=refs.begin(); j!=refs.end(); ++j) + { + std::map::iterator iter = likelihood.find(j->first); + if(iter != likelihood.end()) + { + nwi = j->second; + ni = this->getNi(j->first); + if(ni != 0) + { + //ULOGGER_DEBUG("%d, %f %f %f %f", vw->id(), logNnw, nwi, ni, ( nwi * logNnw ) / ni); + if(_tfIdfNormalized) + { + normalizationRatio = uValue(calculatedWordsRatio, iter->first, -1.0f); + if(normalizationRatio >= 0) + { + iter->second += (( nwi * logNnw ) / ni) * normalizationRatio; + UWARN("for id=%d, normalizationRatio=%f", iter->first, normalizationRatio); + } + else + { + UWARN("not found calculatedWordsRatio for id=%d", iter->first); + iter->second += ( nwi * logNnw ) / ni; + } + } + else + { + iter->second += ( nwi * logNnw ) / ni; + } + } + } + } + } + } + } + } + } + + ULOGGER_DEBUG("compute likelihood... %f s", timer.ticks()); + return likelihood; + } + else + { + return Memory::computeLikelihood(signature, signatureIds); + } + +} + +int KeypointMemory::getNi(int signatureId) const +{ + int ni = 0; + const Signature * s = this->getSignature(signatureId); + if(s) // Must be a SurfSignature + { + ni = ((KeypointSignature *)s)->getWords().size(); + } + else + { + _dbDriver->getSurfNi(signatureId, ni); + } + return ni; +} + +class PreUpdateThread : public UThreadNode +{ +public: + PreUpdateThread(VWDictionary * vwp) : _vwp(vwp) {} + ~PreUpdateThread() {} +private: + void mainLoop() { + if(_vwp) + { + _vwp->update(); + } + this->kill(); + } + VWDictionary * _vwp; +}; + +Signature * KeypointMemory::createSignature(int id, const SMState * smState, bool keepRawData) +{ + PreUpdateThread preUpdateThread(_vwd); + + UTimer timer; + timer.start(); + std::list keypoints; + std::list > descriptors; + const IplImage * image = 0; + + if(smState) + { + if(_parallelized) + { + this->cleanUnusedWords(); + preUpdateThread.start(); + } + + int treeSize= this->getWorkingMemSize() + this->getStMemSize(); + int nbCommonWords = 0; + if(treeSize > 0) + { + nbCommonWords = _vwd->getTotalActiveReferences() / treeSize; + } + + if(!smState->isDescriptorsProvided()) + { + image = smState->getImage(); + if(image && _keypointDetector) + { + keypoints = _keypointDetector->generateKeypoints(image); + ULOGGER_DEBUG("time keypoints = %fs", timer.ticks()); + } + + ULOGGER_DEBUG("ratio=%f, treeSize=%d, nbCommonWords=%d", _badSignRatio, treeSize, nbCommonWords); + + if(keypoints.size() && keypoints.size() >= _badSignRatio * nbCommonWords) + { + descriptors = _keypointDescriptor->generateDescriptors(image, keypoints); + } + } + else + { + if(smState->getSensorStates().size() >= _badSignRatio * nbCommonWords) + { + descriptors = smState->getSensorStates(); + keypoints = smState->getKeypoints(); + } + image = smState->getImage(); + } + } + + preUpdateThread.join(); // Wait the dictionary to be updated + + std::list wordIds; + if(descriptors.size()) + { + ULOGGER_DEBUG("time descriptor and memory update (size=%d) = %fs", descriptors.begin()->size(), timer.ticks()); + wordIds = _vwd->addNewWords(descriptors, descriptors.begin()->size(), id); + ULOGGER_DEBUG("time addNewWords %fs", timer.ticks()); + } + else + { + ULOGGER_WARN("id %d is a bad signature", id); + } + + std::multimap words; + if(wordIds.size() > 0) + { + std::list::iterator kpIter = keypoints.begin(); + for(std::list::iterator iter=wordIds.begin(); iter!=wordIds.end(); ++iter) + { + if(kpIter != keypoints.end()) + { + words.insert(std::pair(*iter, *kpIter)); + ++kpIter; + } + else + { + words.insert(std::pair(*iter, cv::KeyPoint())); + } + } + } + + KeypointSignature * ks = new KeypointSignature(words, id, image, keepRawData); + ULOGGER_DEBUG("time new signature (id=%d) %fs", id, timer.ticks()); + if(words.size()) + { + ks->setEnabled(true); // All references are already activated in the dictionary at this point (see _vwd->addNewWords()) + } + return ks; +} + +Signature * KeypointMemory::getSignatureLtMem(int id) +{ + Signature * s = Memory::getSignatureLtMem(id); + if(s) + { + std::list lid; + lid.push_back(id); + this->enableWordsRef(lid); + } + return s; +} + +void KeypointMemory::disableWordsRef(int signatureId) +{ + ULOGGER_DEBUG("id=%d", signatureId); + + KeypointSignature * ss = dynamic_cast(this->_getSignature(signatureId)); + if(ss && ss->isEnabled()) + { + const std::multimap & words = ss->getWords(); + const std::list & keys = uUniqueKeys(words); + int count = _vwd->getTotalActiveReferences(); + // First remove all references + for(std::list::const_iterator i=keys.begin(); i!=keys.end(); ++i) + { + _vwd->removeAllWordRef(*i, signatureId); + } + + count -= _vwd->getTotalActiveReferences(); + ss->setEnabled(false); + ULOGGER_DEBUG("%d words total ref removed from signature %d...", count, ss->id()); + } +} + +void KeypointMemory::cleanUnusedWords() +{ + ULOGGER_DEBUG(""); + if(_vwd->isIncremental()) + { + std::vector removedWords = _vwd->getUnusedWords(); + + if(removedWords.size()) + { + // remove them from the dictionary + _vwd->removeWords(removedWords); + + for(unsigned int i=0; iasyncSave(removedWords[i]); + } + else + { + delete removedWords[i]; + } + } + } + ULOGGER_DEBUG("%d words removed...", removedWords.size()); + } +} + +void KeypointMemory::enableWordsRef(const std::list & signatureIds) +{ + ULOGGER_DEBUG("size=%d", signatureIds.size()); + UTimer timer; + timer.start(); + + std::map refsToChange; // + + std::set oldWordIds; + std::list surfSigns; + for(std::list::const_iterator i=signatureIds.begin(); i!=signatureIds.end(); ++i) + { + KeypointSignature * ss = dynamic_cast(this->_getSignature(*i)); + if(ss && !ss->isEnabled()) + { + surfSigns.push_back(ss); + std::list uniqueKeys = uUniqueKeys(ss->getWords()); + + //Find words in the signature which they are not in the current dictionary + for(std::list::const_iterator k=uniqueKeys.begin(); k!=uniqueKeys.end(); ++k) + { + if(_vwd->getWord(*k) == 0) + { + //std::map::iterator iter = _wordRefsToChange.find(*k); + //if(iter != _wordRefsToChange.end()) + //{ + // ss->changeWordsRef(iter->first, iter->second); + // uniqueKeys.push_back(iter->second); + //} + //else + if(oldWordIds.find(*k) == oldWordIds.end()) + { + oldWordIds.insert(oldWordIds.end(), *k); + } + else + { + //UDEBUG("*k=%d", *k); + } + } + } + } + } + + ULOGGER_DEBUG("oldWordIds.size()=%d, getOldIds time=%fs", oldWordIds.size(), timer.ticks()); + + // the words were deleted, so try to math it with an active word + std::list vws; + if(oldWordIds.size() && _dbDriver) + { + _dbDriver->loadWords(std::list(oldWordIds.begin(), oldWordIds.end()), vws); // get the descriptors + } + ULOGGER_DEBUG("loading words(%d) time=%fs", oldWordIds.size(), timer.ticks()); + + + if(vws.size()) + { + //Search in the dictionary + std::vector vwActiveIds = _vwd->findNN(vws, _reactivatedWordsComparedToNewWords); + ULOGGER_DEBUG("find active ids (number=%d) time=%fs", vws.size(), timer.ticks()); + int i=0; + for(std::list::iterator iterVws=vws.begin(); iterVws!=vws.end(); ++iterVws) + { + if(vwActiveIds[i] > 0) + { + //ULOGGER_DEBUG("Match found %d with %d", (*iterVws)->id(), vwActiveIds[i]); + refsToChange.insert(refsToChange.end(), std::pair((*iterVws)->id(), vwActiveIds[i])); + if((*iterVws)->isSaved() || !_dbDriver) + { + delete (*iterVws); + } + else + { + _dbDriver->asyncSave(*iterVws); + } + } + else + { + //add to dictionary + _vwd->addWord(*iterVws); + } + ++i; + } + ULOGGER_DEBUG("Added %d to dictionary, time=%fs", vws.size()-refsToChange.size(), timer.ticks()); + + //update the global references map and update the signatures reactivated + for(std::map::const_iterator iter=refsToChange.begin(); iter != refsToChange.end(); ++iter) + { + //uInsert(_wordRefsToChange, (const std::pair)*iter); // This will be used to change references in the database + for(std::list::iterator j=surfSigns.begin(); j!=surfSigns.end(); ++j) + { + (*j)->changeWordsRef(iter->first, iter->second); + } + } + ULOGGER_DEBUG("changing ref, total=%d, time=%fs", refsToChange.size(), timer.ticks()); + } + + int count = _vwd->getTotalActiveReferences(); + + // Reactivate references and signatures + for(std::list::iterator j=surfSigns.begin(); j!=surfSigns.end(); ++j) + { + const std::list & keys = uKeys((*j)->getWords()); + // Add all references + for(std::list::const_iterator i=keys.begin(); i!=keys.end(); ++i) + { + _vwd->addWordRef(*i, (*j)->id()); + } + if(keys.size()) + { + (*j)->setEnabled(true); + } + } + + count = _vwd->getTotalActiveReferences() - count; + ULOGGER_DEBUG("%d words total ref added from %d signatures, time=%fs...", count, surfSigns.size(), timer.ticks()); +} + +int KeypointMemory::forget(const std::list & ignoredIds) +{ + ULOGGER_DEBUG(""); + int signaturesRemoved = 0; + if(_vwd->isIncremental()) + { + int newWords = 0; + int wordsRemoved = 0; + + // Get how many new words added for the last run... + newWords = _vwd->getNotIndexedWordsCount(); + + // So we need to remove at least "newWords" words from the + // dictionary to respect the limit. + while(wordsRemoved < newWords) + { + KeypointSignature * s = dynamic_cast(this->getRemovableSignature(ignoredIds)); + if(s) + { + ++signaturesRemoved; + this->moveToTrash(s); + wordsRemoved = _vwd->getUnusedWordsSize(); + } + else + { + break; + } + } + ULOGGER_DEBUG("newWords=%d, wordsRemoved=%d", newWords, wordsRemoved); + } + else + { + signaturesRemoved = Memory::forget(ignoredIds) ; + } + ULOGGER_DEBUG("signatures removed = %d", signaturesRemoved); + return signaturesRemoved; +} + +int KeypointMemory::reactivateSignatures(const std::list & ids, unsigned int maxLoaded, unsigned int maxTouched) +{ + // get the signatures, if not in the working memory, they + // will be loaded from the database in an more efficient way + // than how it is done in the Memory + + ULOGGER_DEBUG(""); + UTimer timer; + std::list idsToLoad; + unsigned int touched = 0; + std::map::iterator wmIter; + for(std::list::const_iterator i=ids.begin(); i!=ids.end(); ++i) + { + if(!this->getSignature(*i) && !uContains(idsToLoad, *i)) + { + if(!maxLoaded || idsToLoad.size() < maxLoaded) + { + //When loaded from the long-term memory, the signature + // is automatically added on top of the working memory + idsToLoad.push_back(*i); + } + } + else if(touched < maxTouched) + { + this->touch(*i); + } + ++touched; + } + + ULOGGER_DEBUG("idsToLoad = %d", idsToLoad.size()); + + std::list reactivatedSigns; + if(_dbDriver) + { + _dbDriver->loadKeypointSignatures(idsToLoad, reactivatedSigns, true); + } + std::list idsLoaded; + for(std::list::iterator i=reactivatedSigns.begin(); i!=reactivatedSigns.end(); ++i) + { + idsLoaded.push_back((*i)->id()); + //append to working memory + this->addSignatureToWm(*i); + } + this->enableWordsRef(idsLoaded); + ULOGGER_DEBUG("time = %fs", timer.ticks()); + return reactivatedSigns.size(); +} + +void KeypointMemory::moveToTrash(Signature * s) +{ + if(s) + { + this->disableWordsRef(s->id()); + } + Memory::moveToTrash(s); +} + +void KeypointMemory::dumpMemory(std::string directory) const +{ + this->dumpDictionary((directory+"DumpMemoryWordRef.txt").c_str(), (directory+"DumpMemoryWordDesc.txt").c_str()); + Memory::dumpMemory(directory); +} + +void KeypointMemory::dumpDictionary(const char * fileNameRef, const char * fileNameDesc) const +{ + if(_vwd) + { + _vwd->exportDictionary(fileNameRef, fileNameDesc); + } +} + +void KeypointMemory::dumpSignatures(const char * fileNameSign) const +{ + FILE* foutSign = 0; +#ifdef _MSC_VER + fopen_s(&foutSign, fileNameSign, "w"); +#else + foutSign = fopen(fileNameSign, "w"); +#endif + + if(foutSign) + { + fprintf(foutSign, "SignatureID WordsID...\n"); + const std::map & signatures = this->getSignatures(); + for(std::map::const_iterator iter=signatures.begin(); iter!=signatures.end(); ++iter) + { + fprintf(foutSign, "%d ", iter->first); + const KeypointSignature * ss = dynamic_cast(iter->second); + if(ss) + { + const std::multimap & ref = ss->getWords(); + for(std::multimap::const_iterator jter=ref.begin(); jter!=ref.end(); ++jter) + { + fprintf(foutSign, "%d ", (*jter).first); + } + } + fprintf(foutSign, "\n"); + } + fclose(foutSign); + } +} + +} // namespace rtabmap diff --git a/corelib/src/KeypointMemory.h b/corelib/src/KeypointMemory.h new file mode 100644 index 0000000000..527873fb73 --- /dev/null +++ b/corelib/src/KeypointMemory.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef KEYPOINTMEMORY_H_ +#define KEYPOINTMEMORY_H_ + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include "Memory.h" + +namespace rtabmap { + +class VWDictionary; +class VisualWord; +class KeypointDetector; +class KeypointDescriptor; + +class RTABMAP_EXP KeypointMemory : public Memory +{ +public: + enum DetectorStrategy {kDetectorSurf, kDetectorStar, kDetectorSift, kDetectorUndef}; + enum DescriptorStrategy {kDescriptorSurf, kDescriptorColorSurf, kDescriptorLaplacianSurf, kDescriptorSift, kDescriptorHueSurf, kDescriptorUndef}; + +public: + KeypointMemory(const ParametersMap & parameters = ParametersMap()); + virtual ~KeypointMemory(); + + virtual void parseParameters(const ParametersMap & parameters); + virtual bool init(const std::string & dbDriverName, const std::string & dbUrl, bool dbOverwritten = false, const ParametersMap & parameters = ParametersMap()); + virtual std::map computeLikelihood(const Signature * signature, const std::set & signatureIds = std::set()) const; + virtual int forget(const std::list & ignoredIds = std::list()); + virtual int reactivateSignatures(const std::list & ids, unsigned int maxLoaded, unsigned int maxTouched); + virtual void dumpMemory(std::string directory) const; + virtual void dumpSignatures(const char * fileNameSign) const; + + void dumpDictionary(const char * fileNameRef, const char * fileNameDesc) const; + + const KeypointDetector * getKeypointDetector() const {return _keypointDetector;} + const KeypointDescriptor * getKeypointDescriptor() const {return _keypointDescriptor;} + const VWDictionary * getVWD() const {return _vwd;} + DetectorStrategy detectorStrategy() const; + +protected: + virtual Signature * getSignatureLtMem(int id); + virtual void addSignatureToStm(Signature * signature, const std::list > & actions = std::list >()); + virtual void clear(); + virtual void moveToTrash(Signature * s); + virtual void preUpdate(); + virtual void merge(const Signature * from, Signature * to, MergingStrategy s); + +private: + virtual Signature * createSignature(int id, const SMState * rawData, bool keepRawData=false); + void disableWordsRef(int signatureId); + void enableWordsRef(const std::list & signatureIds); + void cleanUnusedWords(); + int getNi(int signatureId) const; + +private: + std::list _commonWords; + VWDictionary * _vwd; + KeypointDetector * _keypointDetector; + KeypointDescriptor * _keypointDescriptor; + //std::map _wordRefsToChange; + bool _reactivatedWordsComparedToNewWords; + float _badSignRatio;; + bool _tfIdfLikelihoodUsed; + bool _parallelized; + bool _tfIdfNormalized; +}; + +} + +#endif /* KEYPOINTMEMORY_H_ */ diff --git a/corelib/src/Memory.cpp b/corelib/src/Memory.cpp new file mode 100644 index 0000000000..36c7d8258b --- /dev/null +++ b/corelib/src/Memory.cpp @@ -0,0 +1,1643 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "Memory.h" +#include "Signature.h" +#include "rtabmap/core/DBDriverFactory.h" +#include "rtabmap/core/DBDriver.h" +#include "utilite/UtiLite.h" +#include "rtabmap/core/Parameters.h" +#include "rtabmap/core/RtabmapEvent.h" +#include "rtabmap/core/SMState.h" +#include "Node.h" + +namespace rtabmap { + +const int Memory::kIdStart = 1; +const int Memory::kIdVirtual = -1; +const int Memory::kIdInvalid = 0; + +Memory::Memory(const ParametersMap & parameters) : + _dbDriver(0), + _similarityThreshold(Parameters::defaultMemSimilarityThr()), + _similarityOnlyLast(Parameters::defaultMemSimilarityOnlyLast()), + _rawDataKept(Parameters::defaultMemRawDataKept()), + _idCount(kIdStart), + _lastSignature(0), + _lastLoopClosureId(0), + _incrementalMemory(Parameters::defaultMemIncrementalMemory()), + _maxStMemSize(Parameters::defaultMemMaxStMemSize()), + _commonSignatureUsed(Parameters::defaultMemCommonSignatureUsed()), + _databaseCleaned(Parameters::defaultMemDatabaseCleaned()), + _delayRequired(Parameters::defaultMemDelayRequired()), + _recentWmRatio(Parameters::defaultMemRecentWmRatio()), + _memoryChanged(false) +{ + this->parseParameters(parameters); +} + +bool Memory::init(const std::string & dbDriverName, const std::string & dbUrl, bool dbOverwritten, const ParametersMap & parameters) +{ + ULOGGER_DEBUG(""); + + this->parseParameters(parameters); + + if(!_memoryChanged || dbOverwritten) + { + if(_dbDriver) + { + UEventsManager::post(new RtabmapEventInit("Closing database connection...")); + _dbDriver->closeConnection(); + delete _dbDriver; + _dbDriver = 0; + UEventsManager::post(new RtabmapEventInit("Closing database connection, done!")); + } + + UEventsManager::post(new RtabmapEventInit("Clearing memory...")); + this->clear(); + UEventsManager::post(new RtabmapEventInit("Clearing memory, done!")); + } + else + { + UEventsManager::post(new RtabmapEventInit("Clearing memory...")); + this->clear(); + UEventsManager::post(new RtabmapEventInit("Clearing memory, done!")); + + if(_dbDriver) + { + UEventsManager::post(new RtabmapEventInit("Closing database connection...")); + _dbDriver->closeConnection(); + delete _dbDriver; + _dbDriver = 0; + UEventsManager::post(new RtabmapEventInit("Closing database connection, done!")); + } + } + + + // Synchronize with the database + _dbDriver = DBDriverFactory::createDBDriver(dbDriverName, parameters); // inMemory + + bool success = false; + if(_dbDriver) + { + if(dbOverwritten) + { + UEventsManager::post(new RtabmapEventInit(std::string("Deleting database ") + dbUrl + "...")); + UFile::erase(dbUrl); + UEventsManager::post(new RtabmapEventInit(std::string("Deleting database ") + dbUrl + ", done!")); + } + UEventsManager::post(new RtabmapEventInit(std::string("Connecting to database ") + dbUrl + "...")); + if(_dbDriver->openConnection(dbUrl)) + { + success = true; + UEventsManager::post(new RtabmapEventInit(std::string("Connecting to database ") + dbUrl + ", done!")); + + // Load the last working memory... + UEventsManager::post(new RtabmapEventInit(std::string("Loading last signatures..."))); + std::list dbSignatures; + _dbDriver->loadLastSignatures(dbSignatures); + for(std::list::reverse_iterator iter=dbSignatures.rbegin(); iter!=dbSignatures.rend(); ++iter) + { + _signatures.insert(std::pair((*iter)->id(), *iter)); + if(_stMem.size() <= _maxStMemSize) + { + _stMem.insert((*iter)->id()); + } + else + { + _workingMem.insert(std::pair((*iter)->id(), 0.0)); + } + } + UEventsManager::post(new RtabmapEventInit(std::string("Loading last signatures, done! (") + uNumber2str(int(_workingMem.size() + _stMem.size())) + " loaded)")); + + // Assign the last signature + if(_stMem.size()>0) + { + _lastSignature = uValue(_signatures, *_stMem.rbegin(), (Signature*)0); + } + + // Last id + _dbDriver->getLastSignatureId(_idCount); + _idCount += 1; + } + else + { + UEventsManager::post(new RtabmapEventInit(RtabmapEventInit::kError, std::string("Connecting to database ") + dbUrl + ", path is invalid!")); + } + } + else + { + _idCount = kIdStart; + } + + _dbDriver->start(); + + ULOGGER_DEBUG("ids start with %d", _idCount); + return success; +} + +Memory::~Memory() +{ + ULOGGER_DEBUG(""); + if(!_memoryChanged) + { + ULOGGER_DEBUG(""); + if(_dbDriver) + { + _dbDriver->closeConnection(); + delete _dbDriver; + _dbDriver = 0; + } + this->clear(); + } + else + { + ULOGGER_DEBUG(""); + this->clear(); + if(_dbDriver) + { + _dbDriver->emptyTrashes(); + _dbDriver->closeConnection(); + delete _dbDriver; + _dbDriver = 0; + } + } +} + +void Memory::parseParameters(const ParametersMap & parameters) +{ + ULOGGER_DEBUG(""); + ParametersMap::const_iterator iter; + if((iter=parameters.find(Parameters::kMemSimilarityThr())) != parameters.end()) + { + this->setSimilarityThreshold(std::atof((*iter).second.c_str())); + } + if((iter=parameters.find(Parameters::kMemSimilarityOnlyLast())) != parameters.end()) + { + _similarityOnlyLast = uStr2Bool((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kMemRawDataKept())) != parameters.end()) + { + _rawDataKept = uStr2Bool((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kMemIncrementalMemory())) != parameters.end()) + { + _incrementalMemory = uStr2Bool((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kMemDatabaseCleaned())) != parameters.end()) + { + _databaseCleaned = uStr2Bool((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kMemMaxStMemSize())) != parameters.end()) + { + this->setMaxStMemSize(std::atoi((*iter).second.c_str())); + } + if((iter=parameters.find(Parameters::kMemCommonSignatureUsed())) != parameters.end()) + { + this->setCommonSignatureUsed(uStr2Bool((*iter).second.c_str())); + } + if((iter=parameters.find(Parameters::kMemDelayRequired())) != parameters.end()) + { + this->setDelayRequired(std::atoi((*iter).second.c_str())); + } + if((iter=parameters.find(Parameters::kMemRecentWmRatio())) != parameters.end()) + { + this->setRecentWmRatio(std::atof((*iter).second.c_str())); + } + if(_dbDriver) + { + _dbDriver->parseParameters(parameters); + } +} + +void Memory::preUpdate() +{ + _signaturesAdded = 0; + + for(std::map::iterator iter=_workingMem.begin(); iter!=_workingMem.end(); ++iter) + { + iter->second += 1; + } +} + +bool Memory::update(const SMState * smState, std::list > & stats) +{ + ULOGGER_DEBUG(""); + UTimer timer; + UTimer totalTimer; + timer.start(); + + //============================================================ + // Pre update... + //============================================================ + ULOGGER_DEBUG("pre-updating..."); + this->preUpdate(); + stats.push_back(std::pair(std::string("TimingMem/Pre-update/ms"), timer.ticks()*1000)); + ULOGGER_DEBUG("time preUpdate=%f ms", stats.back().second); + + //============================================================ + // Create a signature with the image received. + //============================================================ + Signature * signature = this->createSignature(this->getNextId(), smState, this->isRawDataKept()); + if (signature == 0) + { + return false; + } + + //============================================================ + // Post update... does nothing if not overridden + // Must be before addSignature(), this will detect bad signatures + //============================================================ + //this->postUpdate(); + //stats.insert(std::pair(std::string("TimingMem/Post-update/ms"), timer.ticks()*1000)); + //ULOGGER_DEBUG("time postUpdate=%f ms", stats.at(std::string("TimingMem/Post-update/ms"))); + + + // It will be added to the short-time memory, no need to delete it... + this->addSignatureToStm(signature, smState->getActuatorStates()); + _lastSignature = signature; + + if(_lastLoopClosureId == 0) + { + // If not set use the new one added + _lastLoopClosureId = signature->id(); + } + + stats.push_back(std::pair(std::string("TimingMem/Signature creation/ms"), timer.ticks()*1000)); + ULOGGER_DEBUG("time creating signature=%f ms", stats.back().second); + + //============================================================ + // Comparison step... + // Compare with the X last signatures. If different, add this + // signature like a parent to the memory tree, otherwise add + // it as a child to the similar signature. + //============================================================ + int maxId = 0; + if(_similarityThreshold<1.0f) + { + float maxValue=0; + UTimer t; + maxId = rehearsal(signature, _similarityOnlyLast, maxValue); + UDEBUG("t=%fs", t.ticks()); + stats.push_back(std::pair(std::string("Memory/Rehearsal Max Id/"), maxId)); + stats.push_back(std::pair(std::string("Memory/Rehearsal Value/"), maxValue)); + if(maxId > 0 && maxValue >= _similarityThreshold) + { + if(_incrementalMemory) + { + // maxId -> signature->id() + merged + ULOGGER_DEBUG("Linking old signature %d to id=%d", maxId, signature->id()); + this->addLoopClosureLink(maxId, signature->id(), true); + } + else + { + const Signature * s = this->getSignature(maxId); + if(s) + { + signature->setWeight(signature->getWeight() + 1 + s->getWeight()); + } + else + { + signature->setWeight(signature->getWeight() + 1); + } + } + } + UDEBUG("t=%fs", t.ticks()); + } + stats.push_back(std::pair(std::string("TimingMem/Rehearsal/ms"), timer.ticks()*1000)); + ULOGGER_DEBUG("time rehearsal=%f ms", stats.back().second); + + //============================================================ + // Update the common signature + //============================================================ + if(_commonSignatureUsed) + { + Signature * s = _getSignature(kIdVirtual); + if(s == 0) + { + s = this->createSignature(kIdVirtual, 0); // Create a virtual place + _signatures.insert(std::pair(s->id(), s)); + _workingMem.insert(std::pair(s->id(), 0)); + } + } + else + { + // remove virtual signature + Signature * s = _getSignature(kIdVirtual); + if(s) + { + s = this->createSignature(kIdVirtual, 0); // Create a virtual place + this->moveToTrash(s); + } + } + + if(!_memoryChanged) + { + _memoryChanged = true; + } + + UDEBUG("totalTimer = %fs", totalTimer.ticks()); + + stats.push_back(std::pair(std::string("Memory/Last loop closure/"), _lastLoopClosureId)); + + return true; +} + +void Memory::setSimilarityThreshold(float similarityThreshold) +{ + if(similarityThreshold>=0 && similarityThreshold<=1) + { + _similarityThreshold = similarityThreshold; + } + else + { + ULOGGER_ERROR("similarityThreshold=%f", similarityThreshold); + } +} + +void Memory::setMaxStMemSize(unsigned int maxStMemSize) +{ + if(maxStMemSize == 0) + { + ULOGGER_ERROR("maxStMemSize=%d (must be > 0)", maxStMemSize); + } + else + { + _maxStMemSize = maxStMemSize; + } +} + +void Memory::setDelayRequired(int delayRequired) +{ + if(delayRequired < 0) + { + ULOGGER_ERROR("delayRequired=%d (must be >= 0)", delayRequired); + } + else + { + _delayRequired = delayRequired; + } +} + +void Memory::setRecentWmRatio(float recentWmRatio) +{ + if(recentWmRatio < 0 || recentWmRatio >= 1) + { + ULOGGER_ERROR("recentWmRatio=%f (must be >= 0 and < 1)", recentWmRatio); + } + else + { + _recentWmRatio = recentWmRatio; + } +} + +void Memory::setCommonSignatureUsed(bool commonSignatureUsed) +{ + _commonSignatureUsed = commonSignatureUsed; + if(!_commonSignatureUsed) + { + this->moveToTrash(this->_getSignature(kIdVirtual)); + } +} + + +void Memory::addSignatureToStm(Signature * signature, const std::list > & actions) +{ + UTimer timer; + // add signature on top of the short-time memory + if(signature && !signature->isBadSignature()) + { + UDEBUG("adding %d with a=%d", signature->id(), (int)actions.size()); + // Update neighbors + if(_stMem.size()) + { + _signatures.at(*_stMem.rbegin())->addNeighbor(signature->id(), actions); + // actions are not backward compatible, so set null actions + signature->addNeighbor(*_stMem.rbegin(), std::list >()); + } + + _signatures.insert(_signatures.end(), std::pair(signature->id(), signature)); + _stMem.insert(_stMem.end(), signature->id()); + ++_signaturesAdded; + } + + // Transfer the oldest signature of the short-time memory to the working memory + while(_stMem.size() > _maxStMemSize) + { + // (_delayRequired) Can be forgotten as it enters into WM + _workingMem.insert(_workingMem.end(), std::pair(*_stMem.begin(), _delayRequired)); + _stMem.erase(*_stMem.begin()); + } + UDEBUG("time = %fs", timer.ticks()); +} + +void Memory::addSignatureToWm(Signature * signature) +{ + if(signature) + { + _workingMem.insert(std::pair(signature->id(), 0)); + _signatures.insert(std::pair(signature->id(), signature)); + ++_signaturesAdded; + } + else + { + UERROR("Signature is null ?!?"); + } +} + +const Signature * Memory::getSignature(int id) const +{ + return _getSignature(id); +} + +Signature * Memory::_getSignature(int id) const +{ + //ULOGGER_WARN("get signature %d", id); + return uValue(_signatures, id, (Signature*)0); +} + +Signature * Memory::getSignatureLtMem(int id) +{ + ULOGGER_DEBUG("id=%d", id); + Signature * s = 0; + if(id>0 && _dbDriver) + { + // Look up in the database + _dbDriver->getSignature(id, &s); + if(s) + { + _signatures.insert(std::pair(s->id(), s)); + _workingMem.insert(std::pair(s->id(), 0)); + } + } + return s; +} + +/*int Memory::getId(const Signature * signature) const +{ + return _workingMem.key(signature, ID_INVALID); +}*/ + +std::list Memory::getChildrenIds(int signatureId) const +{ + std::list ids; + if(_dbDriver) + { + _dbDriver->getChildrenIds(signatureId, ids); + } + return ids; +} + +int Memory::getNextId() +{ + return _idCount++; +} + +int Memory::getDatabaseMemoryUsed() const +{ + int memoryUsed = 0; + if(_dbDriver) + { + memoryUsed = _dbDriver->getMemoryUsed()/(1024*1024); //Byte to MB + } + return memoryUsed; +} + +double Memory::getDbSavingTime() const +{ + return _dbDriver?_dbDriver->getEmptyTrashesTime():0; +} + +std::set Memory::getAllSignatureIds() const +{ + std::set ids; + if(_dbDriver) + { + _dbDriver->getAllSignatureIds(ids); + for(std::map::const_iterator iter = _signatures.begin(); iter!=_signatures.end(); ++iter) + { + ids.insert(iter->first); + } + } + return ids; +} + +void Memory::clear() +{ + ULOGGER_DEBUG(""); + + if(_dbDriver) + { + _dbDriver->emptyTrashes(); + _dbDriver->kill(); + } + + // Save some stats to the db, save only when the mem is not empty + if(_dbDriver && (_stMem.size() || _workingMem.size())) + { + unsigned int memSize = _workingMem.size() + _stMem.size(); + if(memSize != _signatures.size()) + { + // this is only a safe check...not supposed to occur. + ULOGGER_ERROR("The number of signatures don't match! _workingMem=%d, _stMem=%d, _signatures=%d", _workingMem.size(), _stMem.size(), _signatures.size()); + } + if(_workingMem.size() && _workingMem.begin()->first < 0) + { + --memSize; + } + + ULOGGER_DEBUG("Adding statistics after run..."); + _dbDriver->addStatisticsAfterRun(memSize, _lastSignature?_lastSignature->id():0, UProcessInfo::getMemoryUsage(), _dbDriver->getMemoryUsed()); + } + ULOGGER_DEBUG(""); + + int minId = -1; + std::map::iterator minIter = _signatures.begin(); + while(minIter != _signatures.end()) + { + if(minIter->first > 0) + { + minId = minIter->first; + break; + } + ++minIter; + } + + ULOGGER_DEBUG(""); + + //Get the tree root (parents) + std::map mem = _signatures; + for(std::map::iterator i=mem.begin(); i!=mem.end(); ++i) + { + if(i->second) + { + ULOGGER_DEBUG("deleting from the working and the short-time memory: %d", i->first); + this->moveToTrash(i->second); + } + } + + if(_workingMem.size() != 0) + { + ULOGGER_ERROR("_workingMem must be empty here, size=%d", _workingMem.size()); + } + _workingMem.clear(); + if(_stMem.size() != 0) + { + ULOGGER_ERROR("_stMem must be empty here, size=%d", _stMem.size()); + } + _stMem.clear(); + + ULOGGER_DEBUG(""); + // Wait until the db trash has finished cleaning the memory + if(_dbDriver) + { + _dbDriver->emptyTrashes(); + ULOGGER_DEBUG(""); + //By default unreferenced all signatures with a loop closure link + if(_databaseCleaned) + { + _dbDriver->executeNoResult(std::string("DELETE FROM Signature WHERE loopClosureId!=0;")); + _dbDriver->executeNoResult(std::string("DELETE FROM Neighbor WHERE NOT EXISTS (SELECT * FROM Signature WHERE Signature.id = Neighbor.sid);")); + _dbDriver->executeNoResult(std::string("DELETE FROM Neighbor WHERE NOT EXISTS (SELECT * FROM Signature WHERE Signature.id = Neighbor.nid);")); + _dbDriver->executeNoResult(std::string("DELETE FROM Map_SS_VW WHERE NOT EXISTS (SELECT * FROM Signature WHERE Signature.id = signatureId);")); + } + + ULOGGER_DEBUG(""); + } + ULOGGER_DEBUG(""); + _lastSignature = 0; + _lastLoopClosureId = 0; + _idCount = kIdStart; + _memoryChanged = false; +} + +/** + * Compute the likelihood of the signature with some others in the memory. If + * the signature ids list is empty, the likelihood will be calculated + * for all signatures in the working time memory. + * If an error occurs, the result is empty. + */ +std::map Memory::computeLikelihood(const Signature * signature, const std::set & signatureIds) const +{ + UTimer timer; + timer.start(); + std::map likelihood; + + if(!signature) + { + ULOGGER_ERROR("The signature is null"); + return likelihood; + } + + if(signatureIds.size() == 0) + { + const std::map & wm = this->getWorkingMem(); + float sumSimilarity = 0.0f; + int nonNulls = 0; + int nulls = 0; + float maxSim = 0.0f; + std::map::const_iterator iter = wm.begin(); + for(; iter!=wm.end(); ++iter) + { + float sim = signature->compareTo(this->getSignature(iter->first)); + likelihood.insert(likelihood.end(), std::pair(iter->first, sim)); + sumSimilarity += sim; + UDEBUG("sim %d with %d = %f", signature->id(), iter->first, sim); + if(sim>maxSim) + { + maxSim = sim; + } + if(sim) + { + ++nonNulls; + } + else + { + ++nulls; + } + } + ULOGGER_DEBUG("sumSimilarity=%f, maxSim=%f, nonNulls=%d, nulls=%d)", sumSimilarity, maxSim, nonNulls, nulls); + } + else + { + for(std::set::const_iterator i=signatureIds.begin(); i != signatureIds.end(); ++i) + { + likelihood.insert(likelihood.end(), std::pair(*i, signature->compareTo(this->getSignature(*i)))); + } + } + + ULOGGER_DEBUG("compute likelihood... %f s", timer.ticks()); + return likelihood; +} + +// Weights of the signatures in the working memory +std::map Memory::getWeights() const +{ + std::map weights; + for(std::map::const_iterator i=_workingMem.begin(); i!=_workingMem.end(); ++i) + { + weights.insert(weights.end(), std::pair(i->first, uValue(_signatures, i->first, (Signature*)0)->getWeight())); + } + return weights; +} + +int Memory::getWeight(int id) const +{ + int weight = 0; + const Signature * s = this->getSignature(id); + if(s) + { + weight = s->getWeight(); + } + else if(_dbDriver) + { + _dbDriver->getWeight(id, weight); + } + return weight; +} + +int Memory::forget(const std::list & ignoredIds) +{ + ULOGGER_DEBUG(""); + int signaturesRemoved = 0; + Signature * s = 0; + int i=0; + // Remove one more than total added during the iteration + while(i++<_signaturesAdded+1 && (s = getRemovableSignature(ignoredIds))) + { + ++signaturesRemoved; + // When a signature is deleted, it notifies the memory + // and it is removed from the memory list + this->moveToTrash(s); + } + return signaturesRemoved; +} + + +int Memory::cleanup(const std::list & ignoredIds) +{ + ULOGGER_DEBUG(""); + + //Cleanup looped signatures + int signaturesRemoved = 0; + Signature * s = 0; + while((s = getRemovableSignature(ignoredIds, true))) + { + this->moveToTrash(s); + ++signaturesRemoved; + } + + + // Cleanup of the STM + // We get a copy because moveTotrash() remove the signature from the _stMem + std::set mem = _stMem; + for(std::set::iterator i=mem.begin(); i!=mem.end(); ++i ) + { + Signature * s = this->_getSignature(*i); + if(!s) + { + ULOGGER_ERROR("Signature not found in STM or WM?!?"); + } + else + { + if((*i > 0 && s->getLoopClosureId() != 0) || + (!_incrementalMemory && _lastSignature && _lastSignature->id() == *i) || + (*i > 0 && s->isBadSignature())) + { + this->moveToTrash(s); + ++signaturesRemoved; + } + } + } + return signaturesRemoved; +} + +void Memory::emptyTrash() +{ + if(_dbDriver) + { + _dbDriver->emptyTrashes(true); + } +} + +void Memory::joinTrashThread() +{ + if(_dbDriver) + { + _dbDriver->join(); + } +} + +int Memory::reactivateSignatures(const std::list & ids, unsigned int maxLoaded, unsigned int maxTouched) +{ + // get the signatures, if not in the working memory, they will be loaded from the database + int count = 0; + std::map::iterator wmIter; + unsigned int touched = 0; + for(std::list::const_iterator i=ids.begin(); i!=ids.end(); ++i) + { + if(((maxLoaded && (unsigned int)count < maxLoaded) || !maxLoaded ) && + _signatures.find(*i) == _signatures.end()) + { + if(getSignatureLtMem(*i)) + { + //When loaded from the long-term memory, the signature + // is automatically added on top of the working memory + ++count; + } + } + else if((wmIter = _workingMem.find(*i)) != _workingMem.end() && touched < maxTouched) + { + wmIter->second = 0; + } + ++touched; + } + return count; +} + +Signature * Memory::getRemovableSignature(const std::list & ignoredIds, bool onlyLoopedSignatures) +{ + //ULOGGER_DEBUG(""); + Signature * removableSignature = 0; + + // Find the last index to check... + const std::map & wm = _workingMem; + ULOGGER_DEBUG("mem.size()=%d, ignoredIds.size()=%d", wm.size(), ignoredIds.size()); + + if(wm.size()) + { + int recentWmMaxSize = _recentWmRatio * float(wm.size()); + bool recentWmImmunized = false; + // look for the position of the lastLoopClosureId in WM + int currentRecentWmSize = 0; + if(_lastLoopClosureId > 0 && _stMem.find(_lastLoopClosureId) == _stMem.end()) + { + // If set, it must be in WM + std::map::const_iterator iter = _workingMem.find(_lastLoopClosureId); + while(iter != _workingMem.end()) + { + ++currentRecentWmSize; + ++iter; + } + if(currentRecentWmSize && currentRecentWmSize < recentWmMaxSize) + { + recentWmImmunized = true; + } + else if(currentRecentWmSize == 0 && _workingMem.size() > 1) + { + UERROR("Last loop closure id not found in WM (%d)", _lastLoopClosureId); + } + UDEBUG("currentRecentWmSize=%d, recentWmMaxSize=%d, _recentWmRatio=%f, end recent wM = %d", currentRecentWmSize, recentWmMaxSize, _recentWmRatio, _lastLoopClosureId); + } + + int timestampThr = _delayRequired; // iterations + UDEBUG("_delayRequired = %d", _delayRequired); + for(std::map::const_iterator memIter = wm.begin(); memIter != wm.end(); ++memIter) + { + if(recentWmImmunized && memIter->first > _lastLoopClosureId) + { + ULOGGER_DEBUG("Reached end of recent working memory (%d)", memIter->first); + break; + } + else if(memIter->first > 0 && + std::find(ignoredIds.begin(), ignoredIds.end(), memIter->first) == ignoredIds.end() && + memIter->first != _lastLoopClosureId) + { + //Not in ignored ids and not the lastLoopClosureId + Signature * s = uValue(_signatures, memIter->first, (Signature*)0); + if(s) + { + //ULOGGER_DEBUG("testing : id=%d, weight=%d, lcId=%d", s->id(), s->getWeight(), s->getLoopClosureId()); + if(onlyLoopedSignatures) + { + if(s->getLoopClosureId() != 0) + { + removableSignature = s; + break; + } + } + else if((!removableSignature) || + (removableSignature->getLoopClosureId() == 0 && s->getWeight() < removableSignature->getWeight()) || + (removableSignature->getLoopClosureId() == 0 && s->getLoopClosureId() != 0)) + { + if(memIter->second < timestampThr) + { + ULOGGER_DEBUG("Ignoring %d because it is too recent : (%d < %d iterations)", memIter->first, memIter->second, timestampThr); + } + else + { + if(removableSignature) + { + ULOGGER_DEBUG("Old removable: id=%d, weight=%d, lcId=%d", removableSignature->id(), removableSignature->getWeight(), removableSignature->getLoopClosureId()); + } + ULOGGER_DEBUG("New removable: id=%d, weight=%d, lcId=%d, age=%d", s->id(), s->getWeight(), s->getLoopClosureId(), memIter->second); + removableSignature = s; + if(removableSignature->getLoopClosureId() != 0) + { + // stop now if a signature with a loop closure link is found + break; + } + } + } + } + else + { + ULOGGER_ERROR("Not supposed to occur!!!"); + } + } + else + { + //ULOGGER_DEBUG("Ignoring id %d", memIter->first); + } + } + } + else + { + ULOGGER_WARN("not enough signatures to get an old one..."); + } + ULOGGER_DEBUG("Removable signature = %d", removableSignature?removableSignature->id():0); + return removableSignature; +} + +void Memory::moveToTrash(Signature * s) +{ + ULOGGER_DEBUG("id=%d", s?s->id():0); + if(s) + { + _workingMem.erase(s->id()); + _stMem.erase(s->id()); + _signatures.erase(s->id()); + + if(s->getLoopClosureId() != 0) + { + //remove it from referenced neighbors + const std::set & neighbors = uKeysSet(s->getNeighbors()); + for(std::set::const_iterator iter = neighbors.begin(); iter!=neighbors.end(); ++iter) + { + Signature * ns = this->_getSignature(*iter); + if(ns) + { + ns->removeNeighbor(s->id()); + } + else if(_dbDriver) + { + UDEBUG("A neighbor is not found (%d)", *iter); + _dbDriver->removeNeighbor(*iter, s->id()); + } + s->removeNeighbor(*iter); + } + + if(s->getWeight() && s->getLoopClosureId() > 0) + { + // redirect loop closure id to children (to fix an error + // in addLoopClosureLink where oldId doesn't exist anymore) + // TODO is there a better way to handle that ? Like a direct bi-directional + // loop closure link instead of iterating over all signatures? + for(std::map::reverse_iterator iter=_signatures.rbegin(); iter!=_signatures.rend(); ++iter) + { + if(iter->second->getLoopClosureId() == s->id()) + { + iter->second->setLoopClosureId(s->getLoopClosureId()); + } + } + } + } + + if(_memoryChanged && + _dbDriver && + !s->isBadSignature() && + s->id()>0 && + (s->getLoopClosureId() == 0 || s->isSaved())) + { + _dbDriver->asyncSave(s); + } + else + { + delete s; + } + } +} + +const Signature * Memory::getLastSignature() const +{ + ULOGGER_DEBUG(""); + return _lastSignature; +} + +Signature * Memory::_getLastSignature() +{ + if(_stMem.size()) + { + return uValue(_signatures, *_stMem.rbegin(), (Signature*)0); + } + return 0; +} + +void Memory::addLoopClosureLink(int oldId, int newId, bool rehearsal) +{ + ULOGGER_INFO("old=%d, new=%d", oldId, newId); + Signature * oldS = _getSignature(oldId); + Signature * newS = _getSignature(newId); + if(oldS && newS) + { + if(oldS->getLoopClosureId() > 0 && oldS->getLoopClosureId() == newS->id()) + { + // do nothing, already merged + newS->setWeight(newS->getWeight() + 1); + } + else if(oldS->getLoopClosureId() > 0) + { + this->addLoopClosureLink(oldS->getLoopClosureId(), newS->id()); + oldS->setLoopClosureId(newS->id()); + } + else if(newS->getLoopClosureId() > 0) + { + ULOGGER_ERROR("Not supposed to occur!"); + } + else + { + if(_stMem.find(oldS->id()) != _stMem.end() && rehearsal) + { + // This happens during rehearsal, use only words of the old signature + this->merge(oldS, newS, kUseOnlyFromMerging); + //this->merge(oldS, newS, kFullMerging); + + if(_lastLoopClosureId == oldS->id()) + { + _lastLoopClosureId = newS->id(); + } + } + else + { + // This happens in a loop closure, use only words of the newest signature + this->merge(oldS, newS, kUseOnlyDestMerging); + //this->merge(oldS, newS, kFullMerging); + + _lastLoopClosureId = newS->id(); + } + + newS->setWeight(newS->getWeight() + 1 + oldS->getWeight()); + oldS->setLoopClosureId(newS->id()); + + // keep actions from the old + + // Update neighbors... + oldS->removeNeighbor(newS->id()); + newS->removeNeighbor(oldS->id()); + + const NeighborsMap & neighbors = oldS->getNeighbors(); + for(NeighborsMap::const_iterator i=neighbors.begin(); i!=neighbors.end(); ++i) + { + ULOGGER_DEBUG("neighbor of old = %d", i->first); + } + + for(NeighborsMap::const_iterator i=neighbors.begin(); i!=neighbors.end(); ++i) + { + if(i->first != newS->id()) + { + ULOGGER_DEBUG("add neighbor %d to %d", i->first, newS->id()); + Signature * neighbor = _getSignature(i->first); + bool newHasNeighbor = newS->hasNeighbor(i->first); + if(!newHasNeighbor) + { + //empty actions... they are not backward compatible + newS->addNeighbor(i->first, std::list >()); + } + if(neighbor) + { + bool oldHasNeighbor = neighbor->hasNeighbor(newS->id()); + if(!oldHasNeighbor) + { + // Use corresponding actions from the old signature + std::list > actions = uValue(neighbor->getNeighbors(), oldS->id(), std::list >()); + neighbor->addNeighbor(newS->id(), actions); + if(newHasNeighbor) + { + UERROR("%d has neighbor %d but not the inverse!?", newS->id(), neighbor->id()); + } + } + else if(!newHasNeighbor && oldHasNeighbor) + { + UERROR("%d has neighbor %d but not the inverse!?", neighbor->id(), newS->id()); + } + } + else if(_dbDriver) + { + ULOGGER_DEBUG("*i=%d not found in WM or STM, modifying it in database...", i->first); + _dbDriver->addNeighbor(i->first, newS->id(), i->second); + } + } + } + } + } + else + { + if(!newS) + { + ULOGGER_FATAL("newId=%d, oldId=%d, Signature %d not found in working/st memories", newId, oldId, newId); + } + if(!oldS) + { + ULOGGER_FATAL("newId=%d, oldId=%d, Signature %d not found in working/st memories", newId, oldId, oldId); + } + } +} + +void Memory::dumpMemory(std::string directory) const +{ + ULOGGER_DEBUG(""); + this->dumpSignatures((directory + "DumpMemorySign.txt").c_str()); + this->dumpMemoryTree((directory + "DumpMemoryTree.txt").c_str()); +} + +void Memory::dumpMemoryTree(const char * fileNameTree) const +{ + FILE* foutTree = 0; + #ifdef _MSC_VER + fopen_s(&foutTree, fileNameTree, "w"); + #else + foutTree = fopen(fileNameTree, "w"); + #endif + + if(foutTree) + { + fprintf(foutTree, "SignatureID ChildsID...\n"); + + for(std::map::const_iterator i=_signatures.begin(); i!=_signatures.end(); ++i) + { + fprintf(foutTree, "%d %d %d\n", i->first, i->second->getLoopClosureId(), i->second->getWeight()); + } + + fclose(foutTree); + } + +} + +int Memory::rehearsal(const Signature * signature, bool onlyLast, float & similarity) +{ + UTimer timer; + similarity = 0; + std::map likelihoodTest; + // Get parents to compare... + std::set mem = _stMem; + mem.erase(signature->id()); + if(onlyLast && mem.size()) + { + int last = *(--mem.end()); + mem.clear(); + mem.insert(last); + } + likelihoodTest = Memory::computeLikelihood(signature, mem); + + UDEBUG("t=%fs", timer.ticks()); + if(likelihoodTest.size() == 0) + { + ULOGGER_WARN("likelihoodTest size == 0 ?"); + return 0; + } + + ULOGGER_DEBUG("Comparing with last signatures..."); + float max = 0; + int maxId = 0; + for(std::map::reverse_iterator i=likelihoodTest.rbegin(); i!=likelihoodTest.rend(); ++i) + { + //ULOGGER_DEBUG("id=%d, value=%f", (*i).first, (*i).second); + if((*i).second > max || max == 0) + { + max = (*i).second; + maxId = (*i).first; + } + } + + /*std::vector values = uValues(likelihoodTest); + float sum = uSum(values); + float mean = sum/values.size(); + float stddev = uStdDev(values, mean); + ULOGGER_DEBUG("sum=%f, Mean=%f, std dev=%f", sum, mean, stddev);*/ + UDEBUG("maxId=%d, maxSim=%f, t=%fs", maxId, max, timer.ticks()); + similarity = max; + return maxId; +} + +void Memory::touch(int signatureId) +{ + std::map::iterator iter = _workingMem.find(signatureId); + if(iter != _workingMem.end()) + { + iter->second = 0; + } +} + +// recursive (return map) +void Memory::getNeighborsId(std::map & ids, int signatureId, unsigned int margin, bool checkInDatabase, int ignoredId) const +{ + //ULOGGER_DEBUG("signatureId=%d, neighborsMargin=%d", signatureId, margin); + if(signatureId>0 && margin > 0) + { + // Look up in the short time memory if all ids are here, if not... load them from the database + const Signature * s = this->getSignature(signatureId); + std::set neighborIds; + if(s) + { + neighborIds = uKeysSet(s->getNeighbors()); + } + else if(checkInDatabase && _dbDriver) + { + //ULOGGER_DEBUG("DB Neighbors of %d, Margin=%d", signatureId, margin); + _dbDriver->getNeighborIds(signatureId, neighborIds); + } + + if(checkInDatabase && neighborIds.size() == 0) + { + UERROR("Signature %d doesn't have neighbor!?", signatureId); + } + + for(std::set::iterator i=neighborIds.begin(); i!=neighborIds.end();) + { + std::map::iterator iter = ids.find(*i); + if(*i != ignoredId && + (iter == ids.end() || (unsigned int)iter->second < margin) ) + { + if(iter != ids.end()) + { + iter->second = margin; + } + else + { + ids.insert(std::pair(*i, margin)); + } + ++i; + } + else + { + neighborIds.erase(i++); + } + } + + for(std::set::const_iterator i=neighborIds.begin(); i!=neighborIds.end(); ++i) + { + this->getNeighborsId(ids, *i, margin-1, checkInDatabase, signatureId); + } + } +} + +// The data returned must be released +IplImage * Memory::getImage(int id) const +{ + IplImage * img = 0; + if(_dbDriver) + { + _dbDriver->getImage(id, &img); + } + return img; +} + +void Memory::generateGraph(const std::string & fileName, std::set ids) +{ + if(!_dbDriver) + { + UERROR("A database must must loaded first..."); + return; + } + + if(!fileName.empty()) + { + FILE* fout = 0; + #ifdef _MSC_VER + fopen_s(&fout, fileName.c_str(), "w"); + #else + fout = fopen(fileName.c_str(), "w"); + #endif + + if (!fout) + { + UERROR("Cannot open file %s!", fileName.c_str()); + return; + } + + if(ids.size() == 0) + { + _dbDriver->getAllSignatureIds(ids); + } + + const char * colorA = "red"; + const char * colorB = "skyBlue"; + UINFO("Generating map with %d locations", ids.size()+_signatures.size()); + fprintf(fout, "digraph G {\n"); + std::set > linksAdded; + for(std::set::iterator i=ids.begin(); i!=ids.end(); ++i) + { + if(_signatures.find(*i) == _signatures.end()) + { + int id = *i; + int loopId = 0; + _dbDriver->getLoopClosureId(id, loopId); + if(!loopId) + { + NeighborsMap neighbors; + _dbDriver->loadNeighbors(id, neighbors); + int weight = 0; + _dbDriver->getWeight(id, weight); + for(NeighborsMap::iterator iter = neighbors.begin(); iter!=neighbors.end(); ++iter) + { + if(_signatures.find(iter->first) == _signatures.end() /*&& linksAdded.find(std::pair(id, *iter)) == linksAdded.end()*/) + { + int weightNeighbor = 0; + _dbDriver->getWeight(iter->first, weightNeighbor); + linksAdded.insert(std::pair(iter->first, id)); + fprintf(fout, " \"%d\\n%d\" -> \"%d\\n%d\" [label=\"%d\", fontcolor=%s, fontsize=8];\n", + id, + weight, + iter->first, + weightNeighbor, + (int)iter->second.size(), + iter->second.size()>0?colorA:colorB); + } + } + } + } + } + for(std::map::iterator i=_signatures.begin(); i!=_signatures.end(); ++i) + { + int id = i->second->id(); + const NeighborsMap & neighbors = i->second->getNeighbors(); + int weight = i->second->getWeight(); + for(NeighborsMap::const_iterator iter = neighbors.begin(); iter!=neighbors.end(); ++iter) + { + //if(linksAdded.find(std::pair(id, iter->first)) == linksAdded.end() && + // linksAdded.find(std::pair(iter->first, id)) == linksAdded.end()) + { + int weightNeighbor = 0; + const Signature * s = this->getSignature(iter->first); + if(s) + { + weightNeighbor = s->getWeight(); + } + else + { + _dbDriver->getWeight(iter->first, weightNeighbor); + } + linksAdded.insert(std::pair(iter->first, id)); + fprintf(fout, " \"%d\\n%d\" -> \"%d\\n%d\" [label=\"%d\", fontcolor=%s, fontsize=8];\n", + id, + weight, + iter->first, + weightNeighbor, + (int)iter->second.size(), + iter->second.size()>0?colorA:colorB); + } + } + } + fprintf(fout, "}\n"); + fclose(fout); + } +} + +void Memory::cleanLocalGraph(int id, unsigned int margin) +{ + if(margin >= _stMem.size()) + { + UERROR("The margin (%d) must be smaller than the ST size (%d)", margin, _stMem.size()); + return; + } + + UTimer timer; + UDEBUG("Cleaning local graph for location %d", id); + Node root(id); + this->createGraph(&root, margin); + this->cleanGraph(&root); + + UDEBUG("time=%fs", timer.ticks()); +} + +void Memory::cleanLTM(int maxDepth) +{ + UDEBUG(""); + if(!_dbDriver || !_workingMem.size()) + { + return; + } + + UTimer timer; + int wmSize = _signatures.size(); + UDEBUG("Getting %d highest weighted signatures...", wmSize); + + UDEBUG("time clear=%fs", timer.ticks()); + + std::multimap highestWeightedSignatures; + //Add active signatures... + for(std::map::iterator iter = _signatures.begin(); iter!=_signatures.end(); ++iter) + { + Signature * s = iter->second; + highestWeightedSignatures.insert(std::pair(s->getWeight(), s->id())); + } + //Look in the database + UDEBUG("highestWeightedSignatures.size()=%d", highestWeightedSignatures.size()); + _dbDriver->getHighestWeightedSignatures(wmSize, highestWeightedSignatures); + UDEBUG("highestWeightedSignatures.size()=%d", highestWeightedSignatures.size()); + + std::set weightedSignatures; + for(std::map::iterator iter=highestWeightedSignatures.begin(); iter!=highestWeightedSignatures.end(); ++iter) + { + weightedSignatures.insert(iter->second); + UDEBUG("High signature: %d, w=%d", iter->second, iter->first); + } + + int i=0; + for(std::map::iterator iter=highestWeightedSignatures.begin(); iter!=highestWeightedSignatures.end(); ++iter) + { + Node root(iter->second); + UDEBUG("Creating graph around %d (w=%d) [%d/%d]", iter->second, iter->first, i++, highestWeightedSignatures.size()); + this->createGraph(&root, maxDepth, weightedSignatures); + this->cleanGraph(&root); + } + + UDEBUG("time cleaning LTM=%fs", timer.ticks()); +} + +void Memory::cleanGraph(const Node * root) +{ + if(!root) + { + UERROR("root is null"); + return; + } + UDEBUG("Cleaning graph around %d", root->id()); + + UTimer timer; + std::list > paths; + root->expand(paths); + + int i=0; + /*for(std::list >::iterator iter=paths.begin(); iter!=paths.end();++iter) + { + std::stringstream str; + std::list & path = *iter; + for(std::list::iterator jter = path.begin(); jter!=path.end(); ++jter) + { + str << *jter << " "; + } + UDEBUG("Paths[%d], %s", i++, str.str().c_str()); + }*/ + + std::set centerNodes; + for(std::list >::iterator iter=paths.begin(); iter!=paths.end(); ++iter) + { + std::list & path = *iter; + if(path.size() > 2) + { + for(std::list::iterator jter = ++path.begin(); jter!=--path.end(); ++jter) + { + centerNodes.insert(*jter); + } + } + } + + std::set terminalNodes; + for(std::list >::iterator iter=paths.begin(); iter!=paths.end(); ++iter) + { + std::list & path = *iter; + if(centerNodes.find(path.back()) == centerNodes.end()) + { + terminalNodes.insert(path.back()); + //UDEBUG("terminal=%d", path.back()); + } + } + + std::set toRemove; + for(std::list >::iterator iter=paths.begin(); iter!=paths.end();) + { + std::list & path = *iter; + if(centerNodes.find(iter->back()) != centerNodes.end()) + { + if(path.size() > 2) + { + for(std::list::iterator jter = ++path.begin(); jter!=--path.end(); ++jter) + { + toRemove.insert(*jter); + } + } + iter = paths.erase(iter); + } + else + { + ++iter; + } + } + + i=0; + /*for(std::list >::iterator iter=paths.begin(); iter!=paths.end();++iter) + { + std::stringstream str; + std::list & path = *iter; + for(std::list::iterator jter = path.begin(); jter!=path.end(); ++jter) + { + str << *jter << " "; + } + UDEBUG("Paths[%d], %s", i++, str.str().c_str()); + }*/ + + std::set immunized; + while(paths.size()) + { + bool allRemovable = false; + int terminal = paths.begin()->back(); + //UDEBUG("terminal=%d", terminal); + std::multimap > sortedPaths; + for(std::list >::iterator iter=paths.begin(); iter!=paths.end();) + { + std::list & path = *iter; + if(path.back() == terminal) + { + if(terminalNodes.find(terminal) == terminalNodes.end()) + { + allRemovable = true; + if(immunized.find(terminal) == immunized.end()) + { + toRemove.insert(terminal); + } + } + int pathWeight = 0; + if(path.size() > 2) + { + for(std::list::iterator jter = ++path.begin(); jter!=--path.end(); ++jter) + { + if(immunized.find(*jter) == immunized.end()) + { + toRemove.insert(*jter); + } + int w = this->getWeight(*jter); + //UDEBUG("id(%d) w=%d", *jter, w); + pathWeight += w; + } + } + sortedPaths.insert(std::pair >(pathWeight, *iter)); + iter = paths.erase(iter); + } + else + { + ++iter; + } + } + if(sortedPaths.size() && !allRemovable) + { + std::list & path = (--sortedPaths.end())->second; + if(sortedPaths.count((--sortedPaths.end())->first) > 1) + { + int highestWeight = (--sortedPaths.end())->first; + std::map >::iterator iter = --sortedPaths.end(); + --iter; + while(iter->first == highestWeight) + { + if(iter->second.size() < path.size()) + { + path = iter->second; + } + if(iter == sortedPaths.begin()) + { + break; + } + --iter; + } + } + for(std::list::iterator jter = path.begin(); jter!=path.end(); ++jter) + { + toRemove.erase(*jter); + immunized.insert(*jter); + //UDEBUG("Immunized %d", *jter); + } + } + } + + //for(std::set::iterator iter=toRemove.begin(); iter!=toRemove.end(); ++iter) + //{ + // UDEBUG("toRemove=%d",*iter); + //} + + std::string strToRemove; + for(std::set::iterator iter=toRemove.begin(); iter!=toRemove.end();++iter) + { + Signature * s = this->_getSignature(*iter); + if(!s || (s && s->getLoopClosureId() == 0 && _stMem.find(s->id()) == _stMem.end())) + { + _memoryChanged = true; + if(strToRemove.size()) + { + strToRemove.append(" OR "); + } + strToRemove.append("id="); + strToRemove.append(uNumber2str(*iter)); + + if(s) + { + // Hack, to unreference neighbors without redirecting loop closure id to children + s->setLoopClosureId(-1); + this->moveToTrash(s); + } + else if(_dbDriver) + { + // remove reference from active neighbors + std::set neighbors; + _dbDriver->getNeighborIds(*iter, neighbors); + for(std::set::iterator jter=neighbors.begin(); jter!=neighbors.end(); ++jter) + { + s = this->_getSignature(*jter); + if(s) + { + s->removeNeighbor(*iter); + } + } + } + + } + } + + UDEBUG("Removing %s", strToRemove.c_str()); + if(_dbDriver && !strToRemove.empty()) + { + _dbDriver->executeNoResult(std::string("UPDATE Signature SET loopClosureId=-1 WHERE ") + strToRemove); + } + + UDEBUG("time=%fs", timer.ticks()); +} + +//recursive +void Memory::createGraph(Node * parent, unsigned int maxDepth, const std::set & endIds) +{ + if(maxDepth == 0 || !parent) + { + return; + } + + std::map neighbors; + this->getNeighborsId(neighbors, parent->id(), 1, true); + for(std::map::iterator iter=neighbors.begin(); iter!=neighbors.end(); ++iter) + { + if(!parent->isAncestor(iter->first)) + { + Node * n = new Node(iter->first, parent); + if(endIds.find(iter->first) == endIds.end()) + { + this->createGraph(n, maxDepth-1, endIds); + } + } + } +} + +} // namespace rtabmap diff --git a/corelib/src/Memory.h b/corelib/src/Memory.h new file mode 100644 index 0000000000..331209b189 --- /dev/null +++ b/corelib/src/Memory.h @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef MEMORY_H_ +#define MEMORY_H_ + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include "utilite/UEventsHandler.h" +#include "rtabmap/core/Parameters.h" +#include "utilite/UVariant.h" +#include +#include +#include +#include +#include "utilite/UStl.h" +#include + +namespace rtabmap { + +class Signature; +class DBDriver; +class Node; +class SMState; + +class RTABMAP_EXP Memory +{ +public: + static const int kIdStart; + static const int kIdVirtual; + static const int kIdInvalid; + + enum MergingStrategy{kFullMerging, kUseOnlyFromMerging, kUseOnlyDestMerging}; + +public: + Memory(const ParametersMap & parameters = ParametersMap()); + virtual ~Memory(); + + virtual void parseParameters(const ParametersMap & parameters); + bool update(const SMState * rawData, std::list > & stats); + virtual bool init(const std::string & dbDriverName, const std::string & dbUrl, bool dbOverwritten = false, const ParametersMap & parameters = ParametersMap()); + virtual std::map computeLikelihood(const Signature * signature, const std::set & signatureIds = std::set()) const; + virtual int forget(const std::list & ignoredIds = std::list()); + virtual int reactivateSignatures(const std::list & ids, unsigned int maxLoaded, unsigned int maxTouched); + + int cleanup(const std::list & ignoredIds = std::list()); + void emptyTrash(); + void joinTrashThread(); + void addLoopClosureLink(int oldId, int newId, bool rehearsal = false); + void getNeighborsId(std::map & ids, int signatureId, unsigned int margin, bool checkInDatabase = true, int ignoredId = 0) const; + + //getters + unsigned int getWorkingMemSize() const {return _workingMem.size();} + unsigned int getStMemSize() const {return _stMem.size();}; + const std::map & getWorkingMem() const {return _workingMem;} + const std::set & getStMem() const {return _stMem;} + std::list getChildrenIds(int signatureId) const; + bool isRawDataKept() const {return _rawDataKept;} + std::map getWeights() const; + int getWeight(int id) const; + float getSimilarityOnlyLast() const {return _similarityOnlyLast;} + const Signature * getLastSignature() const; + int getDatabaseMemoryUsed() const; // in bytes + double getDbSavingTime() const; + IplImage * getImage(int id) const; + bool isDatabaseCleaned() const {return _databaseCleaned;} + bool isCommonSignatureUsed() const {return _commonSignatureUsed;} + std::set getAllSignatureIds() const; + bool memoryChanged() const {return _memoryChanged;} + const Signature * getSignature(int id) const; + bool isInSTM(int signatureId) const {return _stMem.find(signatureId) != _stMem.end();} + bool isInWM(int signatureId) const {return _workingMem.find(signatureId) != _workingMem.end();} + bool isInLTM(int signatureId) const {return !this->isInSTM(signatureId) && !this->isInWM(signatureId);} + + //setters + void setSimilarityThreshold(float similarityThreshold); + void setSimilarityOnlyLast(int similarityOnlyLast) {_similarityOnlyLast = similarityOnlyLast;} + void setOldSignatureRatio(float oldSignatureRatio); + void setMaxStMemSize(unsigned int maxStMemSize); + void setDelayRequired(int delayRequired); + void setRecentWmRatio(float recentWmRatio); + void setCommonSignatureUsed(bool commonSignatureUsed); + void setRawDataKept(bool rawDataKept) {_rawDataKept = rawDataKept;} + + void dumpMemoryTree(const char * fileNameTree) const; + virtual void dumpMemory(std::string directory) const; + virtual void dumpSignatures(const char * fileNameSign) const {} + void generateGraph(const std::string & fileName, std::set ids = std::set()); + void cleanLocalGraph(int id, unsigned int margin); + void cleanLTM(int maxDepth = 10); + void createGraph(Node * parent, unsigned int maxDepth, const std::set & endIds = std::set()); + +protected: + virtual void preUpdate(); + virtual void postUpdate() {} + virtual void merge(const Signature * from, Signature * to, MergingStrategy s) = 0; + + virtual void addSignatureToStm(Signature * signature, const std::list > & actions = std::list >()); + virtual void clear(); + virtual void moveToTrash(Signature * s); + virtual Signature * getSignatureLtMem(int id); + + void addSignatureToWm(Signature * signature); + Signature * _getSignature(int id) const; + Signature * _getLastSignature(); + Signature * getRemovableSignature(const std::list & ignoredIds = std::list(), bool onlyLoopedSignatures = false); + int getNextId(); + void initCountId(); + int rehearsal(const Signature * signature, bool onlyLast, float & similarity); + void touch(int signatureId); + const std::map & getSignatures() const {return _signatures;} + +private: + void createVirtualSignature(Signature ** signature); + virtual Signature * createSignature(int id, const SMState * rawData, bool keepRawData=false) = 0; + void cleanGraph(const Node * root); +protected: + DBDriver * _dbDriver; + +private: + float _similarityThreshold; + bool _similarityOnlyLast; + bool _rawDataKept; + int _idCount; + Signature * _lastSignature; + int _lastLoopClosureId; + bool _incrementalMemory; + unsigned int _maxStMemSize; + bool _commonSignatureUsed; + bool _databaseCleaned; //if true, delete old signatures in the database + int _delayRequired; + float _recentWmRatio; + bool _memoryChanged; // False by default, become true when Memory::update() is called. + bool _merging; + int _signaturesAdded; + + std::map _signatures; // TODO : check if a signature is already added? although it is not supposed to occur... + std::set _stMem; + std::map _workingMem; // id, timeStamp +}; + +} // namespace rtabmap + +#endif /* MEMORY_H_ */ diff --git a/corelib/src/NearestNeighbor.cpp b/corelib/src/NearestNeighbor.cpp new file mode 100644 index 0000000000..dea2f562b5 --- /dev/null +++ b/corelib/src/NearestNeighbor.cpp @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "NearestNeighbor.h" +#include "utilite/ULogger.h" +#include + +namespace rtabmap +{ + +///////////////////////// +// KdTreeNN +///////////////////////// +KdTreeNN::KdTreeNN(const ParametersMap & parameters) : + _tree(0) +{ + ULOGGER_DEBUG(""); + this->parseParameters(parameters); +} + +KdTreeNN::~KdTreeNN() +{ + if(_tree) + { + cvReleaseFeatureTree(_tree); + } +} + +void KdTreeNN::setData(const cv::Mat & data) +{ + if(_tree) + { + cvReleaseFeatureTree(_tree); + _tree = 0; + } + + // convert to old style mat (data is not copied) + _dataMat = data; + _tree = cvCreateKDTree(&_dataMat); +} + +void KdTreeNN::search(const cv::Mat & queries, cv::Mat & indices, cv::Mat & dists, int knn, int emax) +{ + ULOGGER_DEBUG(""); + if(_tree) + { + // convert to old style mat (data is not copied) + CvMat queriesMat = queries; + CvMat indicesMat = indices; + CvMat distsMat = dists; + cvFindFeatures(_tree, &queriesMat, &indicesMat, &distsMat, knn, emax); + } + else + { + ULOGGER_ERROR("The search tree is not created, setData() must be called first"); + } +} + +void KdTreeNN::search(const cv::Mat & data, const cv::Mat & queries, cv::Mat & indices, cv::Mat & dists, int knn, int emax) const +{ + ULOGGER_DEBUG(""); + CvMat dataMat = data; + CvFeatureTree * tree = cvCreateKDTree(&dataMat); + + if(tree) + { + // convert to old style mat (data is not copied) + CvMat queriesMat = queries; + CvMat indicesMat = indices; + CvMat distsMat = dists; + cvFindFeatures(tree, &queriesMat, &indicesMat, &distsMat, knn, emax); + cvReleaseFeatureTree(tree); + } + else + { + ULOGGER_ERROR("The search tree creation failed ?!?"); + } +} + +void KdTreeNN::parseParameters(const ParametersMap & parameters) +{ + NearestNeighbor::parseParameters(parameters); +} + + + + +///////////////////////// +// FlannKdTreeNN +///////////////////////// +FlannKdTreeNN::FlannKdTreeNN(const ParametersMap & parameters) : + _treeFlannIndex(0), + _strategy(kKDTree) +{ + ULOGGER_DEBUG(""); + this->parseParameters(parameters); +} + +FlannKdTreeNN::~FlannKdTreeNN() { + if(_treeFlannIndex) + { + delete _treeFlannIndex; + } +} + +void FlannKdTreeNN::setData(const cv::Mat & data) +{ + if(_treeFlannIndex) + { + delete _treeFlannIndex; + _treeFlannIndex = 0; + } + + _treeFlannIndex = createIndex(data, _strategy); // using 4 randomized trees + //_treeFlannIndex = new cv::flann::Index(_dataTree, cv::flann::AutotunedIndexParams(0.9, 0.01, 0, 0.1)); // use autotuned parameters +} + +void FlannKdTreeNN::search(const cv::Mat & queries, cv::Mat & indices, cv::Mat & dists, int knn, int emax) +{ + ULOGGER_DEBUG(""); + if(_treeFlannIndex) + { + // Note, the search params is ignored because we use an autotuned created index (see update()) + _treeFlannIndex->knnSearch(queries, indices, dists, knn, cv::flann::SearchParams(emax) ); // maximum number of leafs checked + } + else + { + ULOGGER_ERROR("The search index is not created, setData() must be called first"); + } +} + +void FlannKdTreeNN::search(const cv::Mat & data, const cv::Mat & queries, cv::Mat & indices, cv::Mat & dists, int knn, int emax) const +{ + ULOGGER_DEBUG(""); + cv::flann::Index * index = createIndex(data, _strategy); + // Note, the search params is ignored because we use an autotuned created index (see update()) + index->knnSearch(queries, indices, dists, knn, cv::flann::SearchParams(emax) ); // maximum number of leafs checked + delete index; +} + +void FlannKdTreeNN::parseParameters(const ParametersMap & parameters) +{ + NearestNeighbor::parseParameters(parameters); +} + +enum Strategy{kLinear, kKDTree, kMeans, kComposite, kAutoTuned, kUndefined}; +cv::flann::Index * FlannKdTreeNN::createIndex(const cv::Mat & data, Strategy s) const +{ + cv::flann::Index * index = 0; + switch(s) + { + case kLinear: + index = new cv::flann::Index(data, cv::flann::LinearIndexParams()); + break; + case kKDTree: + index = new cv::flann::Index(data, cv::flann::KDTreeIndexParams()); + break; + case kMeans: + index = new cv::flann::Index(data, cv::flann::KMeansIndexParams()); + break; + case kComposite: + index = new cv::flann::Index(data, cv::flann::CompositeIndexParams()); + break; + case kAutoTuned: + default: + index = new cv::flann::Index(data, cv::flann::AutotunedIndexParams()); + break; + } + return index; +} + + +} diff --git a/corelib/src/NearestNeighbor.h b/corelib/src/NearestNeighbor.h new file mode 100644 index 0000000000..46b8027147 --- /dev/null +++ b/corelib/src/NearestNeighbor.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef NEARESTNEIGHBOR_H_ +#define NEARESTNEIGHBOR_H_ + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include +#include +#include +#include +#include "rtabmap/core/Parameters.h" + +namespace rtabmap +{ + +class VisualWord; + +class RTABMAP_EXP NearestNeighbor +{ +public: + +public: + virtual ~NearestNeighbor() {} + + virtual void setData(const cv::Mat & data) = 0; + + virtual void search( + const cv::Mat & queries, + cv::Mat & indices, + cv::Mat & dists, + int knn = 1, + int emax = 64) = 0; + + virtual void search( + const cv::Mat & data, + const cv::Mat & queries, + cv::Mat & indices, + cv::Mat & dists, + int knn = 1, + int emax = 64) const = 0; + + virtual bool isDist64F() const = 0; + virtual bool isDistSquared() const = 0; + + virtual void parseParameters(const ParametersMap & parameters) {} + +protected: + NearestNeighbor() {} +}; + + + +///////////////////////// +// KdTreeNN +///////////////////////// +class RTABMAP_EXP KdTreeNN : public NearestNeighbor +{ +public: + KdTreeNN(const ParametersMap & parameters = ParametersMap()); + virtual ~KdTreeNN(); + + virtual void setData(const cv::Mat & data); + + virtual void search(const cv::Mat & queries, + cv::Mat & indices, + cv::Mat & dists, + int knn = 1, + int emax = 64); + + virtual void search(const cv::Mat & data, + const cv::Mat & queries, + cv::Mat & indices, + cv::Mat & dists, + int knn = 1, + int emax = 64) const; + + virtual bool isDist64F() const {return true;} + virtual bool isDistSquared() const {return false;} + virtual void parseParameters(const ParametersMap & parameters); +private: + CvFeatureTree * _tree; + CvMat _dataMat; +}; + + + + + + +///////////////////////// +// FlannKdTreeNN +///////////////////////// +class RTABMAP_EXP FlannKdTreeNN : public NearestNeighbor +{ +public: + enum Strategy{kLinear, kKDTree, kMeans, kComposite, kAutoTuned, kUndefined}; + +public: + FlannKdTreeNN(const ParametersMap & parameters = ParametersMap()); + FlannKdTreeNN(Strategy s, const ParametersMap & parameters = ParametersMap()); + virtual ~FlannKdTreeNN(); + + void setStrategy(Strategy s) {if(_strategy!=kUndefined) _strategy = s;} + + virtual void setData(const cv::Mat & data); + + virtual void search(const cv::Mat & queries, + cv::Mat & indices, + cv::Mat & dists, + int knn = 1, + int emax = 64); + + virtual void search(const cv::Mat & data, + const cv::Mat & queries, + cv::Mat & indices, + cv::Mat & dists, + int knn = 1, + int emax = 64) const; + + virtual bool isDist64F() const {return false;} + virtual bool isDistSquared() const {return true;} + virtual void parseParameters(const ParametersMap & parameters); + +private: + cv::flann::Index * createIndex(const cv::Mat & data, Strategy s) const; + +private: + cv::flann::Index * _treeFlannIndex; + Strategy _strategy; +}; + +} + +#endif /* NEARESTNEIGHBOR_H_ */ diff --git a/corelib/src/Node.h b/corelib/src/Node.h new file mode 100644 index 0000000000..7798572468 --- /dev/null +++ b/corelib/src/Node.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef NODE_H_ +#define NODE_H_ + +namespace rtabmap { + +class Node +{ +public: + Node(int id, Node * parent = 0) : + _parent(parent), + _id(id) + { + if(_parent) + { + _parent->addChild(this); + } + } + virtual ~Node() + { + //We copy the set because when a child is destroyed, it is removed from its parent. + std::set children = _children; + _children.clear(); + for(std::set::iterator iter=children.begin(); iter!=children.end(); ++iter) + { + delete *iter; + } + children.clear(); + if(_parent) + { + _parent->removeChild(this); + } + } + int id() const {return _id;} + bool isAncestor(int id) const + { + if(_parent) + { + if(_parent->id() == id) + { + return true; + } + return _parent->isAncestor(id); + } + return false; + } + + void expand(std::list > & paths, std::list currentPath = std::list()) const + { + currentPath.push_back(_id); + if(_children.size() == 0) + { + paths.push_back(currentPath); + return; + } + for(std::set::const_iterator iter=_children.begin(); iter!=_children.end(); ++iter) + { + (*iter)->expand(paths, currentPath); + } + } + +private: + void addChild(Node * child) + { + _children.insert(child); + } + void removeChild(Node * child) + { + _children.erase(child); + } + +private: + std::set _children; + Node * _parent; + int _id; +}; + +} + +#endif /* NODE_H_ */ diff --git a/corelib/src/Parameters.cpp b/corelib/src/Parameters.cpp new file mode 100644 index 0000000000..21154b96a8 --- /dev/null +++ b/corelib/src/Parameters.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "rtabmap/core/Parameters.h" +#include +#include + +namespace rtabmap +{ + +Parameters * Parameters::instance_ = 0; +UDestroyer Parameters::destroyer_; +ParametersMap Parameters::parameters_; + +Parameters::Parameters() +{ +} + +Parameters::~Parameters() +{ +} + +const ParametersMap & Parameters::getDefaultParameters() +{ + return Parameters::getInstance()->getParameters(); +} + +Parameters * Parameters::getInstance() +{ + if(!instance_) + { + instance_ = new Parameters(); + destroyer_.setDoomed(instance_); + } + return instance_; +} + +const ParametersMap & Parameters::getParameters() const +{ + return parameters_; +} + +void Parameters::addParameter(const std::string & key, const std::string & value) +{ + parameters_.insert(ParametersPair(key, value)); +} + +std::string Parameters::getDefaultWorkingDirectory() +{ + std::string path = UDirectory::homeDir(); + if(!path.empty()) + { + UDirectory::makeDir(path += "/Documents"); + UDirectory::makeDir(path += "/RTAB-Map"); + path += "/"; // add trailing separator + } + else + { + UFATAL("Can't get the HOME variable environment!"); + } + return path; +} + +} diff --git a/corelib/src/Rtabmap.cpp b/corelib/src/Rtabmap.cpp new file mode 100644 index 0000000000..dd3f447e1f --- /dev/null +++ b/corelib/src/Rtabmap.cpp @@ -0,0 +1,1618 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "rtabmap/core/Rtabmap.h" +#include "rtabmap/core/RtabmapEvent.h" +#include "rtabmap/core/CameraEvent.h" +#include "rtabmap/core/Version.h" +#include "rtabmap/core/SMState.h" +#include "rtabmap/core/KeypointDetector.h" + +#include "rtabmap/core/Camera.h" +#include "VWDictionary.h" +#include "Signature.h" + +#include "VerifyHypotheses.h" + +#include "KeypointMemory.h" +#include "BayesFilter.h" + +#include "utilite/UtiLite.h" + +#include "SimpleIni.h" + +#include +#include + +#define DB_TYPE "sqlite3" +#define DB_NAME "LTM.db" + +#define LOG_F "LogF.txt" +#define LOG_I "LogI.txt" + +#define GRAPH_FILE_NAME "Graph.dot" + +namespace rtabmap +{ +const char * Rtabmap::kDefaultIniFileName = "rtabmap.ini"; + +Rtabmap::Rtabmap() : + _publishStats(Parameters::defaultRtabmapPublishStats()), + _reactivationDisabled(Parameters::defaultRtabmapDisableReactivation()), + _maxTimeAllowed(Parameters::defaultRtabmapTimeThr()), // 1 sec + _smStateBufferMaxSize(Parameters::defaultRtabmapSMStateBufferSize()), + _minMemorySizeForLoopDetection(Parameters::defaultRtabmapMinMemorySizeForLoopDetection()), + _loopThr(Parameters::defaultRtabmapLoopThr()), + _loopRatio(Parameters::defaultRtabmapLoopRatio()), + _remThr(Parameters::defaultRtabmapReactivationThr()), + _localGraphCleaned(Parameters::defaultRtabmapLocalGraphCleaned()), + _maxRetrieved(Parameters::defaultRtabmapMaxRetrieved()), + _lcHypothesisId(0), + _reactivateId(0), + _highestHypothesisValue(0), + _spreadMargin(0), + _lastLoopClosureId(0), + _vhStrategy(0), + _bayesFilter(0), + _memory(0), + _foutFloat(0), + _foutInt(0), + _graphFileName(GRAPH_FILE_NAME) +{ + ULOGGER_DEBUG("Working directory=%s", Parameters::defaultRtabmapWorkingDirectory().c_str()); + this->setWorkingDirectory(Parameters::defaultRtabmapWorkingDirectory()); + + UEventsManager::addHandler(this); +} + +Rtabmap::~Rtabmap() { + ULOGGER_DEBUG(""); + + UEventsManager::removeHandler(this); + + // Stop the thread first + this->kill(); + + if(_foutFloat) + { + fclose(_foutFloat); + _foutFloat = 0; + } + if(_foutInt) + { + fclose(_foutInt); + _foutInt = 0; + } + + this->releaseAllStrategies(); +} + +std::string Rtabmap::getVersion() +{ + return RTABMAP_VERSION; +} + +void Rtabmap::setupLogFiles() +{ + // Log files + if(_foutFloat) + { + fclose(_foutFloat); + _foutFloat = 0; + } + if(_foutInt) + { + fclose(_foutInt); + _foutInt = 0; + } + +#ifdef _MSC_VER + fopen_s(&_foutFloat, (_wDir+LOG_F).toStdString().c_str(), "a+"); + fopen_s(&_foutInt, (_wDir+LOG_I).toStdString().c_str(), "a+"); +#else + _foutFloat = fopen((_wDir+LOG_F).c_str(), "a+"); + _foutInt = fopen((_wDir+LOG_I).c_str(), "a+"); +#endif + + ULOGGER_DEBUG("Log file (int)=%s", (_wDir+LOG_I).c_str()); + ULOGGER_DEBUG("Log file (float)=%s", (_wDir+LOG_F).c_str()); +} + +void Rtabmap::releaseAllStrategies() +{ + ULOGGER_DEBUG(""); + if(_vhStrategy) + { + delete _vhStrategy; + _vhStrategy = 0; + } + if(_memory) + { + delete _memory; + _memory = 0; + } + if(_bayesFilter) + { + delete _bayesFilter; + _bayesFilter = 0; + } +} + +void Rtabmap::startInit() +{ + if(!_memory || !_vhStrategy || !_bayesFilter) + { + ULOGGER_DEBUG("Rtabmap thread started without all strategies defined..."); + //this->killSafely(); + } +} + +void Rtabmap::pushNewState(State newState, const ParametersMap & parameters) +{ + if(this->isIdle()) + { + this->start(); + } + + ULOGGER_DEBUG("to %d", newState); + + _stateMutex.lock(); + { + _state.push(newState); + _stateParam.push(parameters); + } + _stateMutex.unlock(); + + _newSMStateSem.release(); +} + +void Rtabmap::init(const ParametersMap & parameters) +{ + if(this->isRunning()) + { + pushNewState(kStateChangingParameters, parameters); + } + else + { + this->parseParameters(parameters); + } + setupLogFiles(); +} + +std::string Rtabmap::getIniFilePath() +{ + std::string privatePath = UDirectory::homeDir() + "/.rtabmap"; + if(!UDirectory::exists(privatePath)) + { + UDirectory::makeDir(privatePath); + } + return (privatePath + "/") + kDefaultIniFileName; +} + +void Rtabmap::init(const char * configFile) +{ + std::string config = this->getIniFilePath(); + if(configFile) + { + config = configFile; + } + ULOGGER_DEBUG("file path = %s", config.c_str()); + + // fill ctrl struct with values from the configuration file + ParametersMap param;// = Parameters::defaultParameters; + this->readParameters(config.c_str(), param); + + this->init(param); +} + +void Rtabmap::parseParameters(const ParametersMap & parameters) +{ + ULOGGER_DEBUG(""); + ParametersMap::const_iterator iter; + if((iter=parameters.find(Parameters::kRtabmapPublishStats())) != parameters.end()) + { + _publishStats = uStr2Bool(iter->second.c_str()); + } + if((iter=parameters.find(Parameters::kRtabmapDisableReactivation())) != parameters.end()) + { + _reactivationDisabled = uStr2Bool(iter->second.c_str()); + } + if((iter=parameters.find(Parameters::kRtabmapTimeThr())) != parameters.end()) + { + _maxTimeAllowed = std::atof(iter->second.c_str()); + } + if((iter=parameters.find(Parameters::kRtabmapLoopThr())) != parameters.end()) + { + _loopThr = std::atof(iter->second.c_str()); + } + if((iter=parameters.find(Parameters::kRtabmapLoopRatio())) != parameters.end()) + { + _loopRatio = std::atof(iter->second.c_str()); + } + if((iter=parameters.find(Parameters::kRtabmapReactivationThr())) != parameters.end()) + { + _remThr = std::atof(iter->second.c_str()); + } + if((iter=parameters.find(Parameters::kRtabmapSMStateBufferSize())) != parameters.end()) + { + _smStateBufferMaxSize = std::atoi(iter->second.c_str()); + } + if((iter=parameters.find(Parameters::kRtabmapMinMemorySizeForLoopDetection())) != parameters.end()) + { + _minMemorySizeForLoopDetection = std::atoi(iter->second.c_str()); + } + if((iter=parameters.find(Parameters::kRtabmapWorkingDirectory())) != parameters.end()) + { + this->setWorkingDirectory(iter->second.c_str()); + } + if((iter=parameters.find(Parameters::kRtabmapLocalGraphCleaned())) != parameters.end()) + { + _localGraphCleaned = uStr2Bool(iter->second.c_str()); + } + if((iter=parameters.find(Parameters::kRtabmapMaxRetrieved())) != parameters.end()) + { + _maxRetrieved = std::atoi(iter->second.c_str()); + } + + + // By default, we create our strategies if they are not already created. + // If they already exists, we check the parameters if a change is requested + + if(!_memory) + { + UEventsManager::post(new RtabmapEventInit(RtabmapEventInit::kInitializing)); + UEventsManager::post(new RtabmapEventInit("Creating memory...")); + _memory = new KeypointMemory(parameters); + UEventsManager::post(new RtabmapEventInit("Creating memory, done!")); + _memory->init(DB_TYPE, _wDir + DB_NAME, false, parameters); + UEventsManager::post(new RtabmapEventInit(RtabmapEventInit::kInitialized)); + } + else + { + _memory->parseParameters(parameters); + } + + VhStrategy vhStrategy = kVhUndef; + // Verifying hypotheses strategy + if((iter=parameters.find(Parameters::kRtabmapVhStrategy())) != parameters.end()) + { + vhStrategy = (VhStrategy)std::atoi((*iter).second.c_str()); + } + if(!_vhStrategy || + (vhStrategy!=kVhUndef && ( (vhStrategy == kVhSimple && !dynamic_cast(_vhStrategy)) || + (vhStrategy == kVhEpipolar && !dynamic_cast(_vhStrategy)) ) )) + { + if(_vhStrategy) + { + delete _vhStrategy; + _vhStrategy = 0; + } + switch(vhStrategy) + { + case kVhEpipolar: + _vhStrategy = new VerifyHypothesesEpipolarGeo(parameters); + break; + case kVhSimple: + default: + _vhStrategy = new VerifyHypothesesSimple(parameters); + break; + } + } + else if(_vhStrategy) + { + _vhStrategy->parseParameters(parameters); + } + + // Bayes filter, create one if not exists + if(!_bayesFilter) + { + _bayesFilter = new BayesFilter(parameters); + } + else + { + _bayesFilter->parseParameters(parameters); + } +} + +int Rtabmap::getLoopClosureId() const +{ + return _lcHypothesisId; +} + +int Rtabmap::getLastSignatureId() const +{ + int id = 0; + if(_memory && _memory->getLastSignature()) + { + id = _memory->getLastSignature()->id(); + } + return id; +} + +std::list Rtabmap::getWorkingMem() const +{ + ULOGGER_DEBUG(""); + std::list mem; + if(_memory) + { + mem = uKeysList(_memory->getWorkingMem()); + mem.remove(-1);// Ignore the virtual signature (if here) + } + return mem; +} + +std::map Rtabmap::getWeights() const +{ + ULOGGER_DEBUG(""); + std::map weights; + if(_memory) + { + weights = _memory->getWeights(); + weights.erase(-1);// Ignore the virtual signature (if here) + } + return weights; +} + +std::set Rtabmap::getStMem() const +{ + ULOGGER_DEBUG(""); + std::set mem; + if(_memory) + { + mem = _memory->getStMem(); + } + return mem; +} + +int Rtabmap::getTotalMemSize() const +{ + ULOGGER_DEBUG(""); + int memSize = 0; + if(_memory) + { + const Signature * s =_memory->getLastSignature(); + if(s) + { + memSize = s->id(); + } + } + return memSize; +} + +void Rtabmap::killCleanup() +{ + _smStateBufferMutex.lock(); + { + for(std::list::iterator i=_smStateBuffer.begin(); i!=_smStateBuffer.end(); ++i) + { + delete(*i); + } + _smStateBuffer.clear(); + } + _smStateBufferMutex.unlock(); + + //this->addImage(0); // this will post the newImage semaphore + _newSMStateSem.release(); +} + +void Rtabmap::mainLoop() +{ + State state = kStateDetecting; + ParametersMap parameters; + + _stateMutex.lock(); + { + if(!_state.empty() && !_stateParam.empty()) + { + state = _state.top(); + _state.pop(); + parameters = _stateParam.top(); + _stateParam.pop(); + } + } + _stateMutex.unlock(); + + switch(state) + { + case kStateDetecting: + if(_memory && _vhStrategy && _bayesFilter) + { + this->process(); + } + else // kIdle + { + ULOGGER_DEBUG("RTAB-Map is not initialized... please call init() or post a STATE_CHANGING_STRATEGY RtabmapEvent...Sleeping 1 sec..."); + uSleep(1000); + } + break; + case kStateChangingParameters: + this->parseParameters(parameters); + break; + case kStateReseting: + if(_memory) + { + if(_memory) + { + UEventsManager::post(new RtabmapEventInit(RtabmapEventInit::kInitializing)); + _memory->init(DB_TYPE, _wDir + DB_NAME); + UEventsManager::post(new RtabmapEventInit(RtabmapEventInit::kInitialized)); + } + if(_bayesFilter) + { + _bayesFilter->reset(); + } + } + _reactivateId = 0; + _highestHypothesisValue = 0; + _spreadMargin = 0; + this->setupLogFiles(); + break; + case kStateDumpingMemory: + if(_memory) + { + _memory->dumpMemory(this->getWorkingDir()); + } + break; + case kStateDumpingPrediction: + if(_memory && _bayesFilter) + { + this->dumpPrediction(); + } + break; + case kStateGeneratingGraph: + if(_memory) + { + _memory->joinTrashThread(); // make sure the trash is flushed + _memory->generateGraph(this->getGraphFileName()); + } + break; + case kStateDeletingMemory: + if(_memory) + { + UEventsManager::post(new RtabmapEventInit(RtabmapEventInit::kInitializing)); + _memory->init(DB_TYPE, _wDir + DB_NAME, true); + UEventsManager::post(new RtabmapEventInit(RtabmapEventInit::kInitialized)); + } + else + { + UFile::erase(_wDir + DB_NAME); + } + if(_bayesFilter) + { + _bayesFilter->reset(); + } + _reactivateId = 0; + _highestHypothesisValue = 0; + _spreadMargin = 0; + this->setupLogFiles(); + break; + default: + uSleep(100); + ULOGGER_ERROR("not supposed to be here..."); + break; + } +} + +void Rtabmap::handleEvent(UEvent* event) +{ + if(this->isKilled()) + { + return; + } + + if(_state.empty()) // If empty, RTAB-Map is detecting + { + if(event->getClassName().compare("SMStateEvent") == 0) + { + SMStateEvent * e = (SMStateEvent*)event; + SMState * data = e->getDataOwnership(); + this->addSMState(data); + } + } + if(event->getClassName().compare("RtabmapEventCmd") == 0) + { + RtabmapEventCmd * rtabmapEvent = (RtabmapEventCmd*)event; + RtabmapEventCmd::Cmd cmd = rtabmapEvent->getCmd(); + if(cmd == RtabmapEventCmd::kCmdResetMemory) + { + if(_memory) + { + ULOGGER_DEBUG("CMD_RESET_MEMORY"); + pushNewState(kStateReseting); + } + } + else if(cmd == RtabmapEventCmd::kCmdDumpMemory) + { + if(_memory) + { + ULOGGER_DEBUG("CMD_DUMP_MEMORY"); + pushNewState(kStateDumpingMemory); + } + } + else if(cmd == RtabmapEventCmd::kCmdDumpPrediction) + { + if(_memory && _bayesFilter) + { + ULOGGER_DEBUG("CMD_DUMP_PREDICTION"); + pushNewState(kStateDumpingPrediction); + } + } + else if(cmd == RtabmapEventCmd::kCmdGenerateGraph) + { + if(_memory && !rtabmapEvent->getStr().empty()) + { + this->setGraphFileName(rtabmapEvent->getStr()); + ULOGGER_DEBUG("CMD_GENERATE_GRAPH"); + pushNewState(kStateGeneratingGraph); + } + } + else if(cmd == RtabmapEventCmd::kCmdDeleteMemory) + { + ULOGGER_DEBUG("CMD_DELETE_MEMORY"); + pushNewState(kStateDeletingMemory); + } + } + if(event->getClassName().compare("ParamEvent") == 0) + { + ULOGGER_DEBUG("changing parameters"); + pushNewState(kStateChangingParameters, ((ParamEvent*)event)->getParameters()); + } +} + +void Rtabmap::process() +{ + ULOGGER_DEBUG(""); + + //============================================================ + // Initialization + //============================================================ + UTimer timer; + UTimer timerTotal; + double timeMemoryUpdate = 0; + double timeCleaningNeighbors = 0; + double timeReactivations = 0; + double timeLikelihoodCalculation = 0; + double timePosteriorCalculation = 0; + double timeHypothesesCreation = 0; + double timeHypothesesValidation = 0; + double timeRealTimeLimitReachedProcess = 0; + double timeMemoryCleanup = 0; + double timeEmptyingMemoryTrash = 0; + double timeStatsCreation = 0; + std::list > memUpdateStats; + + int refId = Memory::kIdInvalid; + float hypothesisRatio = 0.0f; // Only used for statistics + + std::map likelihood; + std::map adjustedLikelihood; + std::map posterior; + std::list > hypotheses; + std::list > reactivateHypotheses; + + std::map childCount; + int rejectLoopReason = 0; + unsigned int signaturesReactivated = 0; + + const Signature * signature = 0; + SMState * smState = 0; + + _lcHypothesisId = 0; + _actions.clear(); + + std::list reactivatedIds; + int directNeighborsNotReactivated = 0; + + // TODO Do a pre-update (build dicitonary), Reactivation parallelized with SURF, addNewWords + + //============================================================ + // Wait for an image... + //============================================================ + ULOGGER_INFO("getting data..."); + smState = this->getSMState(); + if(!smState) + { + ULOGGER_INFO("data is null..."); + return; + } + else if(!_state.empty()) + { + ULOGGER_INFO("State changed while waiting.. aborting processing..."); + delete smState; + return; + } + + timer.start(); + timerTotal.start(); + + //============================================================ + // Memory Update + //============================================================ + ULOGGER_INFO("Updating memory..."); + if(!_memory->update(smState, memUpdateStats)) + { + ULOGGER_ERROR("Not supposed to be here..."); + delete smState; + return; + } + signature = _memory->getLastSignature(); + if(!signature) + { + ULOGGER_ERROR("Not supposed to be here..."); + delete smState; + return; + } + ULOGGER_INFO("Processing signature %d", signature->id()); + refId = signature->id(); + timeMemoryUpdate = timer.ticks(); + ULOGGER_INFO("timeMemoryUpdate=%f", timeMemoryUpdate); + + //if(!signature->isBadSignature()) + { + // Before the next step, make sure the trash has finished + _memory->joinTrashThread(); + timeEmptyingMemoryTrash = timer.ticks(); + ULOGGER_INFO("Time emptying memory trash = %f...", timeEmptyingMemoryTrash); + + + //============================================================ + // Loop closure neighbors reactivation + // If a loop closure occurred the last frame, activate neighbors + // for the likelihood + //============================================================ + if(!_reactivationDisabled && _reactivateId > 0 ) + { + //Load neighbors + ULOGGER_INFO("Reactivating signatures... around id=%d", _reactivateId); + std::list signaturesToReactivate; + //the bayesFilter is supposed to be created here... + unsigned int margin = _bayesFilter->getPredictionLC().size()-2; // Match the neighborhood (Bayes filter) + std::map neighbors; + unsigned int delta = _spreadMargin; + UTimer timeGetN; + + if(_localGraphCleaned && margin < _memory->getStMemSize()) + { + _memory->cleanLocalGraph(_reactivateId, margin); + timeCleaningNeighbors = timeGetN.ticks(); + ULOGGER_DEBUG("Time cleaning local graph around %d = %fs", _reactivateId, timeCleaningNeighbors); + } + + + _memory->getNeighborsId(neighbors, _reactivateId, margin+delta); + ULOGGER_DEBUG("margin=%d, neighbors.size=%d, time=%fs", margin+delta, neighbors.size(), timeGetN.ticks()); + + + // Sort by margin distance + reactivatedIds.push_back(_reactivateId); + int m = margin+delta; + std::list directNeighbors; + //Priority to locations near in space + while(m > 0) + { + std::list idsFront; + std::list idsBack; + for(std::map::reverse_iterator iter=neighbors.rbegin(); iter!=neighbors.rend(); ++iter) + { + //Don't include signatures already in the short-time memory + if(iter->second == m && _memory->isInSTM(iter->first) == 0) + { + //Priority to locations near in time + if(iter->first <= (_reactivateId + m) && iter->first >= (_reactivateId - m)) + { + idsFront.push_back(iter->first); + } + else + { + idsBack.push_back(iter->first); + } + ULOGGER_DEBUG("N id=%d, margin=%d", iter->first, iter->second); + if(m == int(margin+delta)) + { + directNeighbors.push_back(iter->first); + } + } + } + uAppend(reactivatedIds, idsFront); + uAppend(reactivatedIds, idsBack); + --m; + } + + // Max 2 signatures retrieved + signaturesReactivated = _memory->reactivateSignatures(reactivatedIds, _maxRetrieved, margin * 2 + 1); + + // Check if all direct neighbors are loaded (for statistics) + for(std::list::iterator iter = directNeighbors.begin(); iter != directNeighbors.end(); ++iter) + { + if(_memory->getSignature(*iter) == 0) + { + ++directNeighborsNotReactivated; + } + } + + // The reactivatedIds is used to ignore signatures when forgetting, + // limit the size to neighborhood size + while(reactivatedIds.size() > margin * 2 + 1) + { + reactivatedIds.pop_back(); + } + } + timeReactivations = timer.ticks(); + ULOGGER_INFO("timeReactivations=%f", timeReactivations); + + // If the working memory is empty, don't do the detection. It happens when it + // is the first time the detector is started (there needs some images to + // fill the short-time memory before a signature is added to the working memory). + if(_memory->getWorkingMemSize()) + { + //============================================================ + // Likelihood computation + // Get the likelihood of the new signature + // with all images contained in the working time memory + reactivated. + //============================================================ + ULOGGER_INFO("computing likelihood..."); + likelihood = _memory->computeLikelihood(signature); + + // Adjust the likelihood + adjustedLikelihood = likelihood; + this->adjustLikelihood(adjustedLikelihood); + + timeLikelihoodCalculation = timer.ticks(); + ULOGGER_INFO("timeLikelihoodCalculation=%f",timeLikelihoodCalculation); + + //============================================================ + // Apply the Bayes filter + // Posterior = Likelihood x Prior + //============================================================ + ULOGGER_INFO("getting posterior..."); + + // Compute the posterior + posterior = _bayesFilter->computePosterior(_memory, adjustedLikelihood); + timePosteriorCalculation = timer.ticks(); + ULOGGER_INFO("timePosteriorCalculation=%f",timePosteriorCalculation); + + for(std::map::iterator iter=posterior.begin(); iter!=posterior.end(); ++iter) + { + UDEBUG("posterior (%d) = %f", iter->first, iter->second); + } + + //============================================================ + // Select the highest hypothesis + //============================================================ + ULOGGER_INFO("creating hypotheses..."); + if(!signature->isBadSignature() && posterior.size()) + { + if(posterior.size() >= _minMemorySizeForLoopDetection) + { + this->selectHypotheses(posterior, hypotheses, true); + } + else + { + ULOGGER_WARN("The memory size is too small (%d) to create hypotheses, " + "min of %d is required. This warning can be safely ignored " + "if the detector has just started from an empty memory.", + posterior.size(), _minMemorySizeForLoopDetection); + } + } + timeHypothesesCreation = timer.ticks(); + ULOGGER_INFO("timeHypothesesCreation=%f",timeHypothesesCreation); + + rejectLoopReason = 1; //Default: NOT_ENOUGH_HYPOTHESES + if(hypotheses.size()) + { + // Loop closure Threshold + if(!signature->isBadSignature() && + likelihood.at(hypotheses.front().first) > 0.0f && + hypotheses.front().second >= _loopThr && + hypotheses.front().second >= _loopRatio*_highestHypothesisValue && + _highestHypothesisValue) + { + //============================================================ + // Hypothesis verification for loop closure with geometric + // information (like the epipolar geometry or using the local map + // associated with the signature) + //============================================================ + //std::list h; + //h.push_back(hypotheses.front().first); + _lcHypothesisId = hypotheses.front().first; + //_lcHypothesisId = _vhStrategy->verifyHypotheses(h, _memory); + + timeHypothesesValidation = timer.ticks(); + ULOGGER_INFO("timeHypothesesValidation=%f",timeHypothesesValidation); + + //rejectLoopReason = 10 + _vhStrategy->getStatus(); + + // We are tracking the next loop closures, + // reset the retrieval margin + _spreadMargin = 0; + + rejectLoopReason = 10; //Accepted + + } + + if(hypotheses.front().second < _loopThr) + { + rejectLoopReason = 2; + } + + int lastReactivatedId = _reactivateId; + // Recalling Threshold + //this->selectHypotheses(posterior, reactivateHypotheses, false); + //if(reactivateHypotheses.front().second >= _remThr) + //{ + //Priority to single hypotheses over _remThr + // _reactivateId = reactivateHypotheses.front().first; + //} + //else + //if(_lcHypothesisId) + //{ + // _reactivateId = _lcHypothesisId; + //} + //else + //{ + //_reactivateId = hypotheses.front().first; + std::list > hyp; + this->selectHypotheses(posterior, hyp, false); + if(likelihood.at(hyp.front().first) > _remThr) + { + _reactivateId = hyp.front().first; + } + //} + + if(hypotheses.front().second < _loopRatio*_highestHypothesisValue) + { + rejectLoopReason = 3; + if(std::find(reactivatedIds.begin(),reactivatedIds.end(), hypotheses.front().first) != reactivatedIds.end()) + { + // We are loosing the next loop closures (it + // can be temporary occlusions/bad images), + //increment the retrieval margin + ++_spreadMargin; + UDEBUG("Margin++"); + } + else + { + // We lost the next loop closures (a new path is taken), + // reset the retrieval margin + _spreadMargin = 0; + UDEBUG("Margin=0"); + } + } + else if(signaturesReactivated < 2 && _reactivateId == lastReactivatedId) + { + // If we are still, spread margin if no signatures were loaded... + ++_spreadMargin; + UDEBUG("Margin++"); + } + else if(signaturesReactivated >= 2) + { + // We lost the next loop closures (a new path is taken), + // reset the retrieval margin + _spreadMargin = 0; + UDEBUG("Margin=0"); + } + + //for statistic... + hypothesisRatio = _highestHypothesisValue>0?hypotheses.front().second/_highestHypothesisValue:0; + + _highestHypothesisValue = hypotheses.front().second; + } + + //============================================================= + // Update loop closure links + //============================================================= + // Make the new one the parent of the old one + if(_lcHypothesisId>0) + { + _memory->addLoopClosureLink(_lcHypothesisId, signature->id()); + } + } // if(_memory->getWorkingMemSize()) + } // if(!signature->isBadSignature()) + + //============================================================ + // Select next actions + //============================================================ + const Signature * sLoop = 0; + int lcHypothesisReactivated = -1; + int highestHypothesisId = 0; + if(hypotheses.size() > 0) + { + highestHypothesisId = hypotheses.front().first; + } + if(_lcHypothesisId > 0) + { + sLoop = _memory->getSignature(_lcHypothesisId); + //Just for stats + lcHypothesisReactivated = 0; + if(sLoop && sLoop->isSaved()) + { + lcHypothesisReactivated = 1; + } + } + else if(highestHypothesisId > 0) + { + sLoop = _memory->getSignature(highestHypothesisId); + } + + if(sLoop) + { + // select the actions of the neighbor with the highest + // weight (select the more recent is some have the same weight) + const NeighborsMap & neighbors = sLoop->getNeighbors(); + const Signature * s; + int currentWeight = -1; + for(NeighborsMap::const_reverse_iterator iter=neighbors.rbegin(); iter!=neighbors.rend(); ++iter) + { + if(iter->second.size()) + { + s = _memory->getSignature(iter->first); + if(s && s->getWeight() > currentWeight) + { + currentWeight = s->getWeight(); + _actions = iter->second; + } + } + } + } + + //============================================================ + // Prepare statistics + //============================================================ + // Data used for the statistics event and for the log files + int processMemoryUsed = UProcessInfo::getMemoryUsage()/(1024*1024); // MB + int databaseMemoryUsed = _memory->getDatabaseMemoryUsed(); // MB + float responseThr = 0; + int dictionarySize = 0; + int refWordsCount = 0; + int refUniqueWordsCount = 0; + const KeypointSignature * ssRef = 0; + const KeypointSignature * ssLoop = 0; + KeypointMemory * kpMem = dynamic_cast(_memory); + if(kpMem) + { + if(sLoop) + { + ssLoop = dynamic_cast(sLoop); + } + ssRef = dynamic_cast(signature); + responseThr = (float)kpMem->getKeypointDetector()->getAdaptiveResponseThr(); + dictionarySize = kpMem->getVWD()->getVisualWords().size(); + if(ssRef) + { + refWordsCount = ssRef->getWords().size(); + refUniqueWordsCount = uUniqueKeys(ssRef->getWords()).size(); + } + else + { + ULOGGER_WARN("The new signature can't be casted to a KeypointSignature while the Memory is this type ?"); + } + } + + float vpLikelihood = 0.0f; + if(adjustedLikelihood.size() && adjustedLikelihood.begin()->first == -1) + { + vpLikelihood = adjustedLikelihood.begin()->second; + } + + // only prepare statistics if required or when there is a loop closure + Statistics * stat = 0; + if(_lcHypothesisId || _actions.size() || _publishStats) + { + ULOGGER_INFO("sending stats..."); + stat = new Statistics(); + stat->setRefImageId(refId); + if(_lcHypothesisId != Memory::kIdInvalid) + { + stat->addStatistic(Statistics::kLoopClosure_id(), _lcHypothesisId); + stat->setLoopClosureId(_lcHypothesisId); + ULOGGER_INFO("Loop closure detected! With id=%d", _lcHypothesisId); + } + if(_actions.size()) + { + stat->setActions(_actions); + } + if(_publishStats && refId != Memory::kIdInvalid) + { + ULOGGER_INFO("send all stats..."); + stat->setExtended(1); + + stat->setRefImage(smState->getImage()); // The image will be released by the Statistics destructor + + stat->addStatistic(Statistics::kParent_id(), signature->getLoopClosureId()); + if(sLoop) + { + const IplImage * img = sLoop->getImage(); + if(!img && _memory->isRawDataKept()) + { + IplImage * image = _memory->getImage(sLoop->id()); + stat->setLoopClosureImage(&image); // The image will be released by the Statistics destructor + } + else if(img) + { + stat->setLoopClosureImage(img); // The image will be copied + } + } + stat->setPosterior(posterior); + stat->setLikelihood(adjustedLikelihood); + stat->addStatistic(Statistics::kLoopHighest_hypothesis_id(), highestHypothesisId); + stat->addStatistic(Statistics::kLoopHighest_hypothesis_value(), _highestHypothesisValue); + stat->addStatistic(Statistics::kHypothesis_reactivated(), lcHypothesisReactivated); + stat->addStatistic(Statistics::kLoopVp_likelihood(), vpLikelihood); + stat->addStatistic(Statistics::kLoopReactivateId(), _reactivateId); + stat->addStatistic(Statistics::kLoopHypothesis_ratio(), hypothesisRatio); + + // Child count by parent signature on the root of the memory ... for statistics + stat->setWeights(_memory->getWeights()); + + stat->addStatistic(Statistics::kMemoryWorking_memory_size(), _memory->getWorkingMemSize()); + stat->addStatistic(Statistics::kMemoryShort_time_memory_size(), _memory->getStMemSize()); + stat->addStatistic(Statistics::kMemoryDatabase_size(), (float)databaseMemoryUsed); + stat->addStatistic(Statistics::kMemoryProcess_memory_used(), (float)processMemoryUsed); + stat->addStatistic(Statistics::kMemorySignatures_reactivated(), signaturesReactivated); + stat->addStatistic(Statistics::kMemoryImages_buffered(), (float)_smStateBuffer.size()); + + // timing... + stat->addStatistic(Statistics::kTimingMemory_update(), timeMemoryUpdate*1000); + stat->addStatistic(Statistics::kTimingReactivation(), timeReactivations*1000); + stat->addStatistic(Statistics::kTimingLikelihood_computation(), timeLikelihoodCalculation*1000); + stat->addStatistic(Statistics::kTimingPosterior_computation(), timePosteriorCalculation*1000); + stat->addStatistic(Statistics::kTimingHypotheses_creation(), timeHypothesesCreation*1000); + stat->addStatistic(Statistics::kTimingHypotheses_validation(), timeHypothesesValidation*1000); + stat->addStatistic(Statistics::kTimingCleaning_neighbors(), timeCleaningNeighbors*1000); + + // memory update timings + for(std::list >::iterator iter = memUpdateStats.begin(); iter!=memUpdateStats.end(); ++iter) + { + stat->addStatistic(iter->first, iter->second); + } + + // Surf specific parameters + stat->addStatistic(Statistics::kKeypointDictionary_size(), dictionarySize); + stat->addStatistic(Statistics::kKeypointResponse_threshold(), responseThr); + + //Copy keypoints + if(ssRef) + { + stat->setRefWords(ssRef->getWords()); + } + if(ssLoop) + { + //Copy keypoints + stat->setLoopWords(ssLoop->getWords()); + } + + //Epipolar geometry constraint + stat->addStatistic(Statistics::kLoopRejected_reason(), rejectLoopReason); + timeStatsCreation = timer.ticks(); + } + else + { + timeStatsCreation = timer.ticks(); + } + + ULOGGER_INFO("Time creating stats = %f...", timeStatsCreation); + } + + //By default, remove all signatures with a loop closure link if they are not in reactivateIds + //This will also remove rehearsed signatures + int signaturesRemoved = 0; + if(_reactivateId) + { + reactivatedIds.push_back(_reactivateId); + } + signaturesRemoved += _memory->cleanup(reactivatedIds); + timeMemoryCleanup = timer.ticks(); + ULOGGER_INFO("timeMemoryCleanup = %f...", timeMemoryCleanup); + + //============================================================ + // Real time threshold + // If time allowed for the detection exceed the limit of + // real-time, move the oldest signature with less frequency + // entry (from X oldest) from the short term memory to the + // long term memory. + //============================================================ + double totalTime = timerTotal.ticks(); + ULOGGER_INFO("Total time processing = %f...", totalTime); + timer.start(); + if(_maxTimeAllowed != 0 && totalTime>_maxTimeAllowed) + { + ULOGGER_INFO("Removing old signatures because time limit is reached %f>%f...", totalTime, _maxTimeAllowed); + signaturesRemoved = _memory->forget(reactivatedIds); + } + + timeRealTimeLimitReachedProcess = timer.ticks(); + ULOGGER_INFO("Time limit reached processing = %f...", timeRealTimeLimitReachedProcess); + + //Start trashing + _memory->emptyTrash(); + + + //============================================================== + // Finalize statistics and log files + //============================================================== + if(stat) + { + stat->addStatistic(Statistics::kTimingStatistics_creation(), timeStatsCreation*1000); + stat->addStatistic(Statistics::kTimingTotal(), totalTime*1000); + stat->addStatistic(Statistics::kTimingForgetting(), timeRealTimeLimitReachedProcess*1000); + stat->addStatistic(Statistics::kTimingEmptying_memory_trash(), timeEmptyingMemoryTrash*1000); + stat->addStatistic(Statistics::kTimingMemory_cleanup(), timeMemoryCleanup*1000); + stat->addStatistic(Statistics::kMemorySignatures_removed(), signaturesRemoved); + ULOGGER_DEBUG("posting stat event..."); + this->post(new RtabmapEvent(&stat)); // the stat will be automatically deleted + } + + std::list nonNulls; + float sumLikelihoods = 0.0f; + float maxLikelihood = 0.0f; + float mean = 0.0f; + float stddev = 0.0f; + std::map::iterator iter=likelihood.begin(); + if(iter->first == -1) + { + ++iter; + } + for(; iter!=likelihood.end(); ++iter) + { + if(iter->second > 0) + { + sumLikelihoods += iter->second; + nonNulls.push_back(iter->second); + } + if(iter->second > maxLikelihood) + { + maxLikelihood = iter->second; + } + } + if(nonNulls.size()) + { + mean = sumLikelihoods / float(nonNulls.size()); + stddev = uStdDev(nonNulls, mean); + } + + float vpHypothesis = 0; + std::map::iterator vpIter = posterior.find(-1); + if(vpIter != posterior.end()) + { + vpHypothesis = vpIter->second; + } + + // Log info... + // TODO : use a specific class which will handle the RtabmapEvent + if(_foutFloat) + { + + fprintf(_foutFloat, "%f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f\n", + totalTime, + timeMemoryUpdate, + timeReactivations, + timeLikelihoodCalculation, + timePosteriorCalculation, + timeHypothesesCreation, + timeHypothesesValidation, + timeRealTimeLimitReachedProcess, + timeStatsCreation, + _highestHypothesisValue, + vpLikelihood, + maxLikelihood, + sumLikelihoods, + mean, + stddev, + vpHypothesis, + timeEmptyingMemoryTrash); + } + + if(_foutInt) + { + fprintf(_foutInt, "%d %d %d %d %d %d %d %d %d %d %d %d %d %d %lu %d\n", + _lcHypothesisId, + highestHypothesisId, + signaturesRemoved, + int(responseThr), + refWordsCount, + dictionarySize, + int(_memory->getWorkingMemSize()), + rejectLoopReason, + processMemoryUsed, + databaseMemoryUsed, + signaturesReactivated, + lcHypothesisReactivated, + refUniqueWordsCount, + _reactivateId, + nonNulls.size(), + directNeighborsNotReactivated); + } + ULOGGER_INFO("Time logging = %f...", timer.ticks()); + //ULogger::flush(); + delete smState; +} + +void Rtabmap::addSMState(SMState * data) +{ + ULOGGER_DEBUG(""); + if(!data) + { + ULOGGER_ERROR("Data is null?!"); + return; + } + + bool notify = true; + + _smStateBufferMutex.lock(); + { + while(_smStateBufferMaxSize > 0 && _smStateBuffer.size() >= (unsigned int)_smStateBufferMaxSize) + { + ULOGGER_WARN("Data buffer is full, the oldest data is removed to add the new one."); + delete _smStateBuffer.front(); + _smStateBuffer.pop_front(); + notify = false; + } + _smStateBuffer.push_back(data); + } + _smStateBufferMutex.unlock(); + + if(notify) + { + _newSMStateSem.release(); + } + +} + +SMState * Rtabmap::getSMState() +{ + ULOGGER_DEBUG(""); + SMState * data = 0; + + ULOGGER_INFO("waiting for data"); + _newSMStateSem.acquire(); + ULOGGER_INFO("wake-up"); + + _smStateBufferMutex.lock(); + { + if(!_smStateBuffer.empty()) + { + data = _smStateBuffer.front(); + _smStateBuffer.pop_front(); + } + } + _smStateBufferMutex.unlock(); + return data; +} + +// SETTERS +void Rtabmap::setReactivationDisabled(bool reactivationDisabled) +{ + _reactivationDisabled = reactivationDisabled; +} + +void Rtabmap::setMaxTimeAllowed(float maxTimeAllowed) +{ + //must be positive, 0 mean inf time allowed (no time limit) + _maxTimeAllowed = maxTimeAllowed; + if(_maxTimeAllowed < 0) + { + ULOGGER_WARN("maxTimeAllowed < 0, then setting it to 0 (inf)."); + _maxTimeAllowed = 0; + } +} + +void Rtabmap::setDataBufferSize(int size) +{ + if(size < 0) + { + ULOGGER_WARN("size < 0, then setting it to 0 (inf)."); + _smStateBufferMaxSize = 0; + } + else + { + _smStateBufferMaxSize = size; + } +} + +void Rtabmap::setWorkingDirectory(std::string path) +{ + if(path.size() && (path.at(path.size()-1) != '\\' || path.at(path.size()-1) != '/' )) + { + path += "/"; + } + + if(!path.empty() && UDirectory::exists(path)) + { + ULOGGER_DEBUG("Comparing new path \"%s\" with \"%s\"", path.c_str(), _wDir.c_str()); + if(path.compare(_wDir) != 0) + { + _wDir = path; + if(_memory) + { + //clear all buffered images + this->kill(); + UEventsManager::post(new RtabmapEventInit(RtabmapEventInit::kInitializing)); + _memory->init(DB_TYPE, _wDir + DB_NAME); + UEventsManager::post(new RtabmapEventInit(RtabmapEventInit::kInitialized)); + this->kill(); // this will clean a second time the image buffer (if some images were added during the memory initialization) + setupLogFiles(); + this->start(); + } + else + { + setupLogFiles(); + } + } + } + else + { + ULOGGER_ERROR("Directory \"%s\" doesn't exist!", path.c_str()); + } +} + +void Rtabmap::process(SMState * data) +{ + if(!this->isRunning()) + { + this->addSMState(data); + this->process(); + } + else + { + UERROR("The core thread is running!"); + } +} + +void Rtabmap::dumpData() +{ + if(_memory) + { + _memory->dumpMemory(this->getWorkingDir()); + } +} + + +void Rtabmap::adjustLikelihood(std::map & likelihood) const +{ + ULOGGER_DEBUG(""); + UTimer timer; + timer.start(); + if(likelihood.size()==0) + { + return; + } + + // Use only non-null values + std::vector allValues = uValues(likelihood); + std::list values; + for(unsigned int i=0; ifirst == -1 && likelihood.begin()->second) + { + likelihood.begin()->second = 0; + values.pop_front(); + } + + //Compute mean + float mean = uMean(values); + + //Compute std dev + float stdDev = uStdDev(values, mean); + + //Adjust likelihood with mean and standard deviation (see Angeli phd) + float epsilon = 0.0001; + float max = 0.0f; + for(std::map::iterator iter=likelihood.begin(); iter!= likelihood.end(); ++iter) + { + if(iter->second > mean+stdDev && mean) + { + iter->second = (iter->second-(stdDev-epsilon))/mean; + if(iter->second > max) + { + max = iter->second; + } + } + else + { + iter->second = 1; + } + } + + if(likelihood.begin()->first == -1) + { + if(stdDev > epsilon && max) + { + likelihood.begin()->second = mean/stdDev + 1; + } + else + { + likelihood.begin()->second = 2; + ULOGGER_DEBUG("Set vp likelihood to 2, time=%fs", timer.ticks()); + } + if(likelihood.begin()->second<1.0f) + { + likelihood.begin()->second = 1.0f; + } + } + + ULOGGER_DEBUG("mean=%f, stdDev=%f, max=%f time=%fs", mean, stdDev, max, timer.ticks()); +} + +void Rtabmap::selectHypotheses(const std::map & posterior, + std::list > & hypotheses, + bool useNeighborSum) const +{ + ULOGGER_DEBUG(""); + if(!_bayesFilter || !_memory) + { + ULOGGER_ERROR("RTAB-Map must be initialized first!"); + return; + } + if(posterior.size() == 0) + { + ULOGGER_ERROR("Posterior parameter size = 0?"); + return; + } + + float value; + float valueSum; + int id; + unsigned int margin = _bayesFilter->getPredictionLC().size()-2; + UTimer timer; + timer.start(); + UTimer timerGlobal; + timerGlobal.start(); + for(std::map::const_iterator iter = posterior.begin(); iter != posterior.end(); ++iter) + { + value = (*iter).second; + valueSum = (*iter).second; + id = (*iter).first; + + if(id > 0) + { + if(useNeighborSum) + { + //Add neighbor values if they exist + std::map neighbors; + _memory->getNeighborsId(neighbors, id, margin, false); + + for(std::map::iterator jter=neighbors.begin(); jter!=neighbors.end(); ++jter) + { + if(jter->first != iter->first) + { + std::map::const_iterator tmpIter = posterior.find(jter->first); + if(tmpIter!=posterior.end()) + { + valueSum += tmpIter->second; + if((*tmpIter).second > value) + { + value = tmpIter->second; + id = tmpIter->first; + } + } + } + } + //ULOGGER_DEBUG("time=%fs", timer.ticks()); + } + + if(hypotheses.size() && valueSum > hypotheses.front().second) + { + hypotheses.push_front(std::pair(id, valueSum)); + } + else + { + hypotheses.push_back(std::pair(id, valueSum)); + } + } + } + if(hypotheses.size()) + { + ULOGGER_DEBUG("Highest hyposthesis(%d)=%f", hypotheses.front().first, hypotheses.front().second); + } + ULOGGER_DEBUG("time=%fs", timerGlobal.ticks()); +} + +void Rtabmap::dumpPrediction() const +{ + if(_memory && _bayesFilter) + { + const std::map & wm = _memory->getWorkingMem(); + CvMat * prediction = cvCreateMat(wm.size(), wm.size(), CV_32FC1); + std::map keys; + std::map::iterator insertedKeyPos = keys.begin(); + int index = 0; + for(std::map::const_iterator iter=wm.begin(); iter!=wm.end(); ++iter) + { + insertedKeyPos = keys.insert(insertedKeyPos, std::pair(iter->first, index++)); + } + _bayesFilter->generatePrediction(prediction, _memory, keys); + + FILE* fout = 0; + std::string fileName = this->getWorkingDir() + "/DumpPrediction.txt"; + #ifdef _MSC_VER + fopen_s(&fout, fileName.c_str(), "w"); + #else + fout = fopen(fileName.c_str(), "w"); + #endif + + if(fout) + { + for(int i=0; irows; ++i) + { + for(int j=0; jcols; ++j) + { + fprintf(fout, "%f ", prediction->data.fl[j + i*prediction->cols]); + } + fprintf(fout, "\n"); + } + fclose(fout); + } + } + else + { + UWARN("Memory and/or the Bayes filter are not created"); + } +} + +void Rtabmap::readParameters(const char * configFile, ParametersMap & parameters) +{ + CSimpleIniA ini; + ini.SetUnicode(); + ini.LoadFile(configFile); + const CSimpleIniA::TKeyVal * keyValMap = ini.GetSection("Core"); + if(keyValMap) + { + for(CSimpleIniA::TKeyVal::const_iterator iter=keyValMap->begin(); iter!=keyValMap->end(); ++iter) + { + std::string key = (*iter).first.pItem; + key = uReplaceChar(key, '\\', '/'); // Ini files use \ by default for separators, so replace them + ParametersMap::iterator jter = parameters.find(key); + if(jter != parameters.end()) + { + parameters.erase(jter); + } + parameters.insert(ParametersPair(key, (*iter).second)); + } + } + else + { + ULOGGER_WARN("Section \"Core\" in %s doesn't exist... " + "Ignore this warning if the ini file does not exist yet. " + "The ini file will be automatically created when this node will close.", configFile); + } +} + +void Rtabmap::writeParameters(const char * configFile, const ParametersMap & parameters) +{ + CSimpleIniA ini; + ini.SetUnicode(); + ini.LoadFile(configFile); + + for(ParametersMap::const_iterator i=parameters.begin(); i!=parameters.end(); ++i) + { + std::string key = (*i).first; + key = uReplaceChar(key, '/', '\\'); // Ini files use \ by default for separators, so replace the / + ini.SetValue("Core", key.c_str(), (*i).second.c_str(), NULL, true); + } + + ini.SaveFile(configFile); +} + +} // namespace rtabmap diff --git a/corelib/src/RtabmapEvent.cpp b/corelib/src/RtabmapEvent.cpp new file mode 100644 index 0000000000..f16abe9825 --- /dev/null +++ b/corelib/src/RtabmapEvent.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "rtabmap/core/RtabmapEvent.h" + +namespace rtabmap { +std::map Statistics::_defaultData; +bool Statistics::_defaultDataInitialized = false; + +const std::map & Statistics::defaultData() +{ + Statistics stat; + return _defaultData; +} + +Statistics::Statistics() : + _extended(0), + _refImageId(0), + _loopClosureId(0), + _refImage(0), + _loopClosureImage(0) +{ + _defaultDataInitialized = true; +} +Statistics::Statistics(const Statistics & s) : + _extended(0), + _refImageId(0), + _loopClosureId(0), + _refImage(0), + _loopClosureImage(0) +{ + *this = s; +} +Statistics::~Statistics() +{ + if(_refImage) + { + cvReleaseImage(&_refImage); + } + if(_loopClosureImage) + { + cvReleaseImage(&_loopClosureImage); + } +} + +// name format = "Grp/Name/unit" +void Statistics::addStatistic(const std::string & name, float value) +{ + _data.insert(std::pair(name, value)); +} + +//take the ownership of the image, the image will be +//deleted in the 'Statistics' destructor +void Statistics::setRefImage(IplImage ** refImage) +{ + if(_refImage) + cvReleaseImage(&_refImage); + _refImage = *refImage; +} + +// Copy the image +void Statistics::setRefImage(const IplImage * refImage) +{ + if(_refImage) + cvReleaseImage(&_refImage); + if(refImage) + { + _refImage = cvCloneImage(refImage); + } + else + { + _refImage = 0; + } +} + +//take the ownership of the image, the image will be +//deleted in the 'Statistics' destructor +void Statistics::setLoopClosureImage(IplImage ** loopClosureImage) +{ + if(_loopClosureImage) + cvReleaseImage(&_loopClosureImage); + _loopClosureImage = *loopClosureImage; +} + +// Copy the image +void Statistics::setLoopClosureImage(const IplImage * loopClosureImage) +{ + if(_loopClosureImage) + cvReleaseImage(&_loopClosureImage); + if(loopClosureImage) + { + _loopClosureImage = cvCloneImage(loopClosureImage); + } + else + { + _loopClosureImage = 0; + } +} + +Statistics & Statistics::operator=(const Statistics & s) +{ + ULOGGER_DEBUG(""); + _data = s.data(); + if(_refImage) + { + cvReleaseImage(&_refImage); + _refImage = 0; + } + if(_loopClosureImage) + { + cvReleaseImage(&_loopClosureImage); + _loopClosureImage = 0; + } + _extended = s.extended(); + _refImageId = s.refImageId(); + _loopClosureId = s.loopClosureId(); + if(s.refImage()) + { + _refImage = cvCloneImage(s.refImage()); + } + if(s.loopClosureImage()) + { + _loopClosureImage = cvCloneImage(s.loopClosureImage()); + } + _posterior = s.posterior(); + _likelihood = s.likelihood(); + _weights = s.weights(); + _refWords = s.refWords(); + _loopWords = s.loopWords(); + + return *this; +} + +} diff --git a/corelib/src/Signature.cpp b/corelib/src/Signature.cpp new file mode 100644 index 0000000000..82fb17219f --- /dev/null +++ b/corelib/src/Signature.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "Signature.h" +#include "Memory.h" +#include +#include "VerifyHypotheses.h" + +#include "utilite/UtiLite.h" + +namespace rtabmap +{ + +Signature::~Signature() +{ + ULOGGER_DEBUG("id=%d", _id); + if(_image) + { + cvReleaseImage(&_image); + } +} + +Signature::Signature(int id, const IplImage * image, bool keepImage) : + _id(id), + _weight(0), + _loopClosureId(0), + _image(0), + _saved(false), + _width(0), + _height(0) +{ + if(image) + { + _width = image->width; + _height = image->height; + if(keepImage) + { + _image = cvCloneImage(image); + } + } +} + +// Warning, the image returned must be released +const IplImage * Signature::getImage() const +{ + return _image; +} + +void Signature::setImage(const IplImage * image) +{ + if(_image && image) + { + cvReleaseImage(&_image); + _image = cvCloneImage(image); + } + else + { + UWARN("Parameter is null or no image is saved."); + } +} + +// Warning, the matrix returned must be released +CvMat * Signature::compressImage(const IplImage * image) +{ + if(!image) + { + UERROR("The parameter must not be null."); + return 0; + } + // Compress image + int params[3] = {0}; + + //JPEG compression + std::string format = "jpeg"; + params[0] = CV_IMWRITE_JPEG_QUALITY; + params[1] = 80; // default: 80% quality + + //PNG compression + //std::string format = "png"; + //params[0] = CV_IMWRITE_PNG_COMPRESSION; + //params[1] = 9; // default: maximum compression + + std::string extension = '.' + format; + return cvEncodeImage(extension.c_str(), image, params); +} + +// Warning, the image returned must be released +IplImage * Signature::decompressImage(const CvMat * imageCompressed) +{ + if(!imageCompressed) + { + UERROR("The parameter must not be null."); + return 0; + } + return cvDecodeImage(imageCompressed, CV_LOAD_IMAGE_ANYCOLOR); +} + +void Signature::addNeighbors(const NeighborsMap & neighbors) +{ + for(NeighborsMap::const_iterator i=neighbors.begin(); i!=neighbors.end(); ++i) + { + this->addNeighbor(i->first, i->second); + //UDEBUG("%d -> %d, a=%d", this->id(), i->first, i->second.size()); + } +} + +void Signature::addNeighbor(int neighbor, const std::list > & actions) +{ + ULOGGER_DEBUG("Adding neighbor %d to %d with %d actions", neighbor, this->id(), actions.size()); + std::pair inserted = _neighbors.insert(std::pair > >(neighbor, actions)); + //UDEBUG("%d -> %d, a=%d", this->id(), neighbor, actions.size()); + if(!inserted.second) + { + ULOGGER_ERROR("neighbor %d already added to %d", neighbor, this->id()); + return; + } + if(neighbor == _id) + { + ULOGGER_ERROR("same Id ? (%d)", neighbor, this->id()); + return; + } +} + +void Signature::removeNeighbor(int neighbor) +{ + ULOGGER_DEBUG("Removing neighbor %d to %d", neighbor, this->id()); + // we delete the first found because there is not supposed + // to have more than one occurrence of this neighbor (see addNeighbor()) + int erased = _neighbors.erase(neighbor); + if(!erased) + { + ULOGGER_WARN("neighbor %d not found in %d", neighbor, this->id()); + } +} + + + + +//KeypointSignature +KeypointSignature::KeypointSignature( + const std::multimap & words, + int id, + const IplImage * image, + bool keepRawData) : + Signature(id, image, keepRawData), + _words(words), + _enabled(false) +{ +} + +KeypointSignature::KeypointSignature(int id) : + Signature(id), + _enabled(false) +{ +} + +KeypointSignature::~KeypointSignature() +{ +} + +float KeypointSignature::compareTo(const Signature * s) const +{ + const KeypointSignature * ss = dynamic_cast(s); + float similarity = 0; + + if(ss) //Compatible + { + const std::multimap & words = ss->getWords(); + + if(words.size() != 0 && _words.size() != 0) + { + std::list > pairs; + std::list pairsId; + int totalWords = _words.size()>words.size()?_words.size():words.size(); + VerifyHypothesesEpipolarGeo::findPairsDirect(words, _words, pairs, pairsId); + + similarity = float(pairs.size()) / float(totalWords); + + // Adjust similarity with the ratio of words between the signatures + /*float ratio = 1; + if(_words.size() > words.size() && _words.size()) + { + ratio = float(words.size()) / float(_words.size()); + } + else + { + ratio = float(_words.size()) / float(words.size()); + } + + similarity *= ratio;*/ + } + } + return similarity; +} + +void KeypointSignature::changeWordsRef(int oldWordId, int activeWordId) +{ + std::list kps = uValues(_words, oldWordId); + if(kps.size()) + { + _words.erase(oldWordId); + for(std::list::const_iterator iter=kps.begin(); iter!=kps.end(); ++iter) + { + _words.insert(std::pair(activeWordId, (*iter))); + } + } +} + +#define BAD_SIGNATURE_THRESHOLD 0 // elements +bool KeypointSignature::isBadSignature() const +{ + if(_words.size() <= BAD_SIGNATURE_THRESHOLD) + return true; + return false; +} + +void KeypointSignature::removeAllWords() +{ + _words.clear(); +} + +void KeypointSignature::removeWord(int wordId) +{ + _words.erase(wordId); +} + +} diff --git a/corelib/src/Signature.h b/corelib/src/Signature.h new file mode 100644 index 0000000000..4462be0083 --- /dev/null +++ b/corelib/src/Signature.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#pragma once + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include +#include +#include +#include +#include +#include + +//TODO : add copy constructor + +namespace rtabmap +{ + +class Memory; +typedef std::map > > NeighborsMap; + +class RTABMAP_EXP Signature +{ +public: + static CvMat * compressImage(const IplImage * image); + static IplImage * decompressImage(const CvMat * imageCompressed); + +public: + virtual ~Signature(); + + /** + * Must return a value between >=0 and <=1 (1 means 100% similarity) + */ + virtual float compareTo(const Signature * signature) const = 0; + virtual bool isBadSignature() const = 0; + virtual std::string signatureType() const = 0; + + const IplImage * getImage() const; + void setImage(const IplImage * image); + + int id() const {return _id;} + + void addNeighbors(const NeighborsMap & neighbors); + void addNeighbor(int neighborId, const std::list > & actions); + void removeNeighbor(int neighborId); + bool hasNeighbor(int neighborId) const {return _neighbors.find(neighborId) != _neighbors.end();} + void setWeight(int weight) {_weight = weight;} + void setLoopClosureId(int loopClosureId) {_loopClosureId = loopClosureId;} + void setWidth(int width) {_width = width;} + void setHeight(int height) {_height = height;} + void setSaved(bool saved) {_saved = saved;} + + const NeighborsMap & getNeighbors() const {return _neighbors;} + int getWeight() const {return _weight;} + int getLoopClosureId() const {return _loopClosureId;} + int getWidth() const {return _width;} + int getHeight() const {return _height;} + bool isSaved() const {return _saved;} + +protected: + Signature(int id, const IplImage * image = 0, bool keepImage = false); + +private: + int _id; + NeighborsMap _neighbors; // id, [action1, action2, ...] All actions must have the same length + int _weight; + int _loopClosureId; + IplImage * _image; + bool _saved; // If it's saved to bd + int _width; // pixels + int _height; // pixels +}; + + +class KeypointDetector; +class VWDictionary; + +class RTABMAP_EXP KeypointSignature : + public Signature +{ +public: + KeypointSignature( + const std::multimap & words, + int id, + const IplImage * image = 0, + bool keepRawData = false); + KeypointSignature(int id); + + virtual ~KeypointSignature(); + + virtual float compareTo(const Signature * signature) const; + virtual bool isBadSignature() const; + virtual std::string signatureType() const {return "KeypointSignature";}; + + void removeAllWords(); + void removeWord(int wordId); + void changeWordsRef(int oldWordId, int activeWordId); + + void setWords(const std::multimap & words) {_enabled = false;_words = words;} + bool isEnabled() const {return _enabled;} + void setEnabled(bool enabled) {_enabled = enabled;} + const std::multimap & getWords() const {return _words;} + +private: + // Contains all words (Some can be duplicates -> if a word appears 2 + // times in the signature, it will be 2 times in this list) + // Words match with the CvSeq keypoints and descriptors + std::multimap _words; // word + bool _enabled; + +}; + + +} // namespace rtabmap diff --git a/corelib/src/SimpleIni.h b/corelib/src/SimpleIni.h new file mode 100644 index 0000000000..5f4a3fe12a --- /dev/null +++ b/corelib/src/SimpleIni.h @@ -0,0 +1,3258 @@ +/** @mainpage + + +
Library SimpleIni +
File SimpleIni.h +
Author Brodie Thiesfield [code at jellycan dot com] +
Source http://code.jellycan.com/simpleini/ +
Version 4.12 +
+ + Jump to the @link CSimpleIniTempl CSimpleIni @endlink interface documentation. + + @section intro INTRODUCTION + + This component allows an INI-style configuration file to be used on both + Windows and Linux/Unix. It is fast, simple and source code using this + component will compile unchanged on either OS. + + + @section features FEATURES + + - MIT Licence allows free use in all software (including GPL and commercial) + - multi-platform (Windows 95/98/ME/NT/2K/XP/2003, Windows CE, Linux, Unix) + - loading and saving of INI-style configuration files + - configuration files can have any newline format on all platforms + - liberal acceptance of file format + - key/values with no section + - removal of whitespace around sections, keys and values + - support for multi-line values (values with embedded newline characters) + - optional support for multiple keys with the same name + - optional case-insensitive sections and keys (for ASCII characters only) + - saves files with sections and keys in the same order as they were loaded + - preserves comments on the file, section and keys where possible. + - supports both char or wchar_t programming interfaces + - supports both MBCS (system locale) and UTF-8 file encodings + - system locale does not need to be UTF-8 on Linux/Unix to load UTF-8 file + - support for non-ASCII characters in section, keys, values and comments + - support for non-standard character types or file encodings + via user-written converter classes + - support for adding/modifying values programmatically + - compiles cleanly in the following compilers: + - Windows/VC6 (warning level 3) + - Windows/VC.NET 2003 (warning level 4) + - Windows/VC 2005 (warning level 4) + - Linux/gcc (-Wall) + + + @section usage USAGE SUMMARY + + -# Define the appropriate symbol for the converter you wish to use and + include the SimpleIni.h header file. If no specific converter is defined + then the default converter is used. The default conversion mode uses + SI_CONVERT_WIN32 on Windows and SI_CONVERT_GENERIC on all other + platforms. If you are using ICU then SI_CONVERT_ICU is supported on all + platforms. + -# Declare an instance the appropriate class. Note that the following + definitions are just shortcuts for commonly used types. Other types + (PRUnichar, unsigned short, unsigned char) are also possible. + +
Interface Case-sensitive Load UTF-8 Load MBCS Typedef +
SI_CONVERT_GENERIC +
char No Yes Yes #1 CSimpleIniA +
char Yes Yes Yes CSimpleIniCaseA +
wchar_t No Yes Yes CSimpleIniW +
wchar_t Yes Yes Yes CSimpleIniCaseW +
SI_CONVERT_WIN32 +
char No No #2 Yes CSimpleIniA +
char Yes Yes Yes CSimpleIniCaseA +
wchar_t No Yes Yes CSimpleIniW +
wchar_t Yes Yes Yes CSimpleIniCaseW +
SI_CONVERT_ICU +
char No Yes Yes CSimpleIniA +
char Yes Yes Yes CSimpleIniCaseA +
UChar No Yes Yes CSimpleIniW +
UChar Yes Yes Yes CSimpleIniCaseW +
+ #1 On Windows you are better to use CSimpleIniA with SI_CONVERT_WIN32.
+ #2 Only affects Windows. On Windows this uses MBCS functions and + so may fold case incorrectly leading to uncertain results. + -# Call Load() or LoadFile() to load and parse the INI configuration file + -# Access and modify the data of the file using the following functions + +
GetAllSections Return all section names +
GetAllKeys Return all key names within a section +
GetAllValues Return all values within a section & key +
GetSection Return all key names and values in a section +
GetSectionSize Return the number of keys in a section +
GetValue Return a value for a section & key +
SetValue Add or update a value for a section & key +
Delete Remove a section, or a key from a section +
+ -# Call Save() or SaveFile() to save the INI configuration data + + @section iostreams IO STREAMS + + SimpleIni supports reading from and writing to STL IO streams. Enable this + by defining SI_SUPPORT_IOSTREAMS before including the SimpleIni.h header + file. Ensure that if the streams are backed by a file (e.g. ifstream or + ofstream) then the flag ios_base::binary has been used when the file was + opened. + + @section multiline MULTI-LINE VALUES + + Values that span multiple lines are created using the following format. + +
+        key = <<
+
+    Note the following:
+    - The text used for ENDTAG can be anything and is used to find
+      where the multi-line text ends.
+    - The newline after ENDTAG in the start tag, and the newline
+      before ENDTAG in the end tag is not included in the data value.
+    - The ending tag must be on it's own line with no whitespace before
+      or after it.
+    - The multi-line value is modified at load so that each line in the value
+      is delimited by a single '\\n' character on all platforms. At save time
+      it will be converted into the newline format used by the current
+      platform.
+
+    @section comments COMMENTS
+
+    Comments are preserved in the file within the following restrictions:
+    - Every file may have a single "file comment". It must start with the
+      first character in the file, and will end with the first non-comment
+      line in the file.
+    - Every section may have a single "section comment". It will start
+      with the first comment line following the file comment, or the last
+      data entry. It ends at the beginning of the section.
+    - Every key may have a single "key comment". This comment will start
+      with the first comment line following the section start, or the file
+      comment if there is no section name.
+    - Comments are set at the time that the file, section or key is first
+      created. The only way to modify a comment on a section or a key is to
+      delete that entry and recreate it with the new comment. There is no
+      way to change the file comment.
+
+    @section save SAVE ORDER
+
+    The sections and keys are written out in the same order as they were
+    read in from the file. Sections and keys added to the data after the
+    file has been loaded will be added to the end of the file when it is
+    written. There is no way to specify the location of a section or key
+    other than in first-created, first-saved order.
+
+    @section notes NOTES
+
+    - To load UTF-8 data on Windows 95, you need to use Microsoft Layer for
+      Unicode, or SI_CONVERT_GENERIC, or SI_CONVERT_ICU.
+    - When using SI_CONVERT_GENERIC, ConvertUTF.c must be compiled and linked.
+    - When using SI_CONVERT_ICU, ICU header files must be on the include
+      path and icuuc.lib must be linked in.
+    - To load a UTF-8 file on Windows AND expose it with SI_CHAR == char,
+      you should use SI_CONVERT_GENERIC.
+    - The collation (sorting) order used for sections and keys returned from
+      iterators is NOT DEFINED. If collation order of the text is important
+      then it should be done yourself by either supplying a replacement
+      SI_STRLESS class, or by sorting the strings external to this library.
+    - Usage of the  header on Windows can be disabled by defining
+      SI_NO_MBCS. This is defined automatically on Windows CE platforms.
+
+
+    @section licence MIT LICENCE
+
+    The licence text below is the boilerplate "MIT Licence" used from:
+    http://www.opensource.org/licenses/mit-license.php
+
+    Copyright (c) 2006-2008, Brodie Thiesfield
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to deal
+    in the Software without restriction, including without limitation the rights
+    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+    copies of the Software, and to permit persons to whom the Software is furnished
+    to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in
+    all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+    FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+    COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+    IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+    CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef INCLUDED_SimpleIni_h
+#define INCLUDED_SimpleIni_h
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+# pragma once
+#endif
+
+// Disable these warnings in MSVC:
+//  4127 "conditional expression is constant" as the conversion classes trigger
+//  it with the statement if (sizeof(SI_CHAR) == sizeof(char)). This test will
+//  be optimized away in a release build.
+//  4503 'insert' : decorated name length exceeded, name was truncated
+//  4702 "unreachable code" as the MS STL header causes it in release mode.
+//  Again, the code causing the warning will be cleaned up by the compiler.
+//  4786 "identifier truncated to 256 characters" as this is thrown hundreds
+//  of times VC6 as soon as STL is used.
+#ifdef _MSC_VER
+# pragma warning (push)
+# pragma warning (disable: 4127 4503 4702 4786)
+#endif
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifdef SI_SUPPORT_IOSTREAMS
+# include 
+#endif // SI_SUPPORT_IOSTREAMS
+
+#ifdef _DEBUG
+# ifndef assert
+#  include 
+# endif
+# define SI_ASSERT(x)   assert(x)
+#else
+# define SI_ASSERT(x)
+#endif
+
+enum SI_Error {
+    SI_OK       =  0,   //!< No error
+    SI_UPDATED  =  1,   //!< An existing value was updated
+    SI_INSERTED =  2,   //!< A new value was inserted
+
+    // note: test for any error with (retval < 0)
+    SI_FAIL     = -1,   //!< Generic failure
+    SI_NOMEM    = -2,   //!< Out of memory error
+    SI_FILE     = -3    //!< File error (see errno for detail error)
+};
+
+#define SI_UTF8_SIGNATURE     "\xEF\xBB\xBF"
+
+#ifdef _WIN32
+# define SI_NEWLINE_A   "\r\n"
+# define SI_NEWLINE_W   L"\r\n"
+#else // !_WIN32
+# define SI_NEWLINE_A   "\n"
+# define SI_NEWLINE_W   L"\n"
+#endif // _WIN32
+
+#if defined(SI_CONVERT_ICU)
+# include 
+#endif
+
+#if defined(_WIN32)
+# define SI_HAS_WIDE_FILE
+# define SI_WCHAR_T     wchar_t
+#elif defined(SI_CONVERT_ICU)
+# define SI_HAS_WIDE_FILE
+# define SI_WCHAR_T     UChar
+#endif
+
+
+// ---------------------------------------------------------------------------
+//                              MAIN TEMPLATE CLASS
+// ---------------------------------------------------------------------------
+
+/** Simple INI file reader.
+
+    This can be instantiated with the choice of unicode or native characterset,
+    and case sensitive or insensitive comparisons of section and key names.
+    The supported combinations are pre-defined with the following typedefs:
+
+    
+        
Interface Case-sensitive Typedef +
char No CSimpleIniA +
char Yes CSimpleIniCaseA +
wchar_t No CSimpleIniW +
wchar_t Yes CSimpleIniCaseW +
+ + Note that using other types for the SI_CHAR is supported. For instance, + unsigned char, unsigned short, etc. Note that where the alternative type + is a different size to char/wchar_t you may need to supply new helper + classes for SI_STRLESS and SI_CONVERTER. + */ +template +class CSimpleIniTempl +{ +public: + /** key entry */ + struct Entry { + const SI_CHAR * pItem; + const SI_CHAR * pComment; + int nOrder; + + Entry(const SI_CHAR * a_pszItem = NULL, int a_nOrder = 0) + : pItem(a_pszItem) + , pComment(NULL) + , nOrder(a_nOrder) + { } + Entry(const SI_CHAR * a_pszItem, const SI_CHAR * a_pszComment, int a_nOrder) + : pItem(a_pszItem) + , pComment(a_pszComment) + , nOrder(a_nOrder) + { } + Entry(const Entry & rhs) { operator=(rhs); } + Entry & operator=(const Entry & rhs) { + pItem = rhs.pItem; + pComment = rhs.pComment; + nOrder = rhs.nOrder; + return *this; + } + +#if defined(_MSC_VER) && _MSC_VER <= 1200 + /** STL of VC6 doesn't allow me to specify my own comparator for list::sort() */ + bool operator<(const Entry & rhs) const { return LoadOrder()(*this, rhs); } + bool operator>(const Entry & rhs) const { return LoadOrder()(rhs, *this); } +#endif + + /** Strict less ordering by name of key only */ + struct KeyOrder : std::binary_function { + bool operator()(const Entry & lhs, const Entry & rhs) const { + const static SI_STRLESS isLess = SI_STRLESS(); + return isLess(lhs.pItem, rhs.pItem); + } + }; + + /** Strict less ordering by order, and then name of key */ + struct LoadOrder : std::binary_function { + bool operator()(const Entry & lhs, const Entry & rhs) const { + if (lhs.nOrder != rhs.nOrder) { + return lhs.nOrder < rhs.nOrder; + } + return KeyOrder()(lhs.pItem, rhs.pItem); + } + }; + }; + + /** map keys to values */ + typedef std::multimap TKeyVal; + + /** map sections to key/value map */ + typedef std::map TSection; + + /** set of dependent string pointers. Note that these pointers are + dependent on memory owned by CSimpleIni. + */ + typedef std::list TNamesDepend; + + /** interface definition for the OutputWriter object to pass to Save() + in order to output the INI file data. + */ + class OutputWriter { + public: + OutputWriter() { } + virtual ~OutputWriter() { } + virtual void Write(const char * a_pBuf) = 0; + private: + OutputWriter(const OutputWriter &); // disable + OutputWriter & operator=(const OutputWriter &); // disable + }; + + /** OutputWriter class to write the INI data to a file */ + class FileWriter : public OutputWriter { + FILE * m_file; + public: + FileWriter(FILE * a_file) : m_file(a_file) { } + void Write(const char * a_pBuf) { + fputs(a_pBuf, m_file); + } + private: + FileWriter(const FileWriter &); // disable + FileWriter & operator=(const FileWriter &); // disable + }; + + /** OutputWriter class to write the INI data to a string */ + class StringWriter : public OutputWriter { + std::string & m_string; + public: + StringWriter(std::string & a_string) : m_string(a_string) { } + void Write(const char * a_pBuf) { + m_string.append(a_pBuf); + } + private: + StringWriter(const StringWriter &); // disable + StringWriter & operator=(const StringWriter &); // disable + }; + +#ifdef SI_SUPPORT_IOSTREAMS + /** OutputWriter class to write the INI data to an ostream */ + class StreamWriter : public OutputWriter { + std::ostream & m_ostream; + public: + StreamWriter(std::ostream & a_ostream) : m_ostream(a_ostream) { } + void Write(const char * a_pBuf) { + m_ostream << a_pBuf; + } + private: + StreamWriter(const StreamWriter &); // disable + StreamWriter & operator=(const StreamWriter &); // disable + }; +#endif // SI_SUPPORT_IOSTREAMS + + /** Characterset conversion utility class to convert strings to the + same format as is used for the storage. + */ + class Converter : private SI_CONVERTER { + public: + Converter(bool a_bStoreIsUtf8) : SI_CONVERTER(a_bStoreIsUtf8) { + m_scratch.resize(1024); + } + Converter(const Converter & rhs) { operator=(rhs); } + Converter & operator=(const Converter & rhs) { + m_scratch = rhs.m_scratch; + return *this; + } + bool ConvertToStore(const SI_CHAR * a_pszString) { + size_t uLen = SizeToStore(a_pszString); + if (uLen == (size_t)(-1)) { + return false; + } + while (uLen > m_scratch.size()) { + m_scratch.resize(m_scratch.size() * 2); + } + return SI_CONVERTER::ConvertToStore( + a_pszString, + const_cast(m_scratch.data()), + m_scratch.size()); + } + const char * Data() { return m_scratch.data(); } + private: + std::string m_scratch; + }; + +public: + /*-----------------------------------------------------------------------*/ + + /** Default constructor. + + @param a_bIsUtf8 See the method SetUnicode() for details. + @param a_bMultiKey See the method SetMultiKey() for details. + @param a_bMultiLine See the method SetMultiLine() for details. + */ + CSimpleIniTempl( + bool a_bIsUtf8 = false, + bool a_bMultiKey = false, + bool a_bMultiLine = false + ); + + /** Destructor */ + ~CSimpleIniTempl(); + + /** Deallocate all memory stored by this object */ + void Reset(); + + /*-----------------------------------------------------------------------*/ + /** @{ @name Settings */ + + /** Set the storage format of the INI data. This affects both the loading + and saving of the INI data using all of the Load/Save API functions. + This value cannot be changed after any INI data has been loaded. + + If the file is not set to Unicode (UTF-8), then the data encoding is + assumed to be the OS native encoding. This encoding is the system + locale on Linux/Unix and the legacy MBCS encoding on Windows NT/2K/XP. + If the storage format is set to Unicode then the file will be loaded + as UTF-8 encoded data regardless of the native file encoding. If + SI_CHAR == char then all of the char* parameters take and return UTF-8 + encoded data regardless of the system locale. + + \param a_bIsUtf8 Assume UTF-8 encoding for the source? + */ + void SetUnicode(bool a_bIsUtf8 = true) { + if (!m_pData) m_bStoreIsUtf8 = a_bIsUtf8; + } + + /** Get the storage format of the INI data. */ + bool IsUnicode() const { return m_bStoreIsUtf8; } + + /** Should multiple identical keys be permitted in the file. If set to false + then the last value encountered will be used as the value of the key. + If set to true, then all values will be available to be queried. For + example, with the following input: + +
+        [section]
+        test=value1
+        test=value2
+        
+ + Then with SetMultiKey(true), both of the values "value1" and "value2" + will be returned for the key test. If SetMultiKey(false) is used, then + the value for "test" will only be "value2". This value may be changed + at any time. + + \param a_bAllowMultiKey Allow multi-keys in the source? + */ + void SetMultiKey(bool a_bAllowMultiKey = true) { + m_bAllowMultiKey = a_bAllowMultiKey; + } + + /** Get the storage format of the INI data. */ + bool IsMultiKey() const { return m_bAllowMultiKey; } + + /** Should data values be permitted to span multiple lines in the file. If + set to false then the multi-line construct << + SI_CHAR FORMAT + char same format as when loaded (MBCS or UTF-8) + wchar_t UTF-8 + other UTF-8 + + + Note that comments from the original data is preserved as per the + documentation on comments. The order of the sections and values + from the original file will be preserved. + + Any data prepended or appended to the output device must use the the + same format (MBCS or UTF-8). You may use the GetConverter() method to + convert text to the correct format regardless of the output format + being used by SimpleIni. + + To add a BOM to UTF-8 data, write it out manually at the very beginning + like is done in SaveFile when a_bUseBOM is true. + + @param a_oOutput Output writer to write the data to. + + @param a_bAddSignature Prepend the UTF-8 BOM if the output data is in + UTF-8 format. If it is not UTF-8 then this value is + ignored. Do not set this to true if anything has + already been written to the OutputWriter. + + @return SI_Error See error definitions + */ + SI_Error Save( + OutputWriter & a_oOutput, + bool a_bAddSignature = false + ) const; + +#ifdef SI_SUPPORT_IOSTREAMS + /** Save the INI data to an ostream. See Save() for details. + + @param a_ostream String to have the INI data appended to. + + @param a_bAddSignature Prepend the UTF-8 BOM if the output data is in + UTF-8 format. If it is not UTF-8 then this value is + ignored. Do not set this to true if anything has + already been written to the stream. + + @return SI_Error See error definitions + */ + SI_Error Save( + std::ostream & a_ostream, + bool a_bAddSignature = false + ) const + { + StreamWriter writer(a_ostream); + return Save(writer, a_bAddSignature); + } +#endif // SI_SUPPORT_IOSTREAMS + + /** Append the INI data to a string. See Save() for details. + + @param a_sBuffer String to have the INI data appended to. + + @param a_bAddSignature Prepend the UTF-8 BOM if the output data is in + UTF-8 format. If it is not UTF-8 then this value is + ignored. Do not set this to true if anything has + already been written to the string. + + @return SI_Error See error definitions + */ + SI_Error Save( + std::string & a_sBuffer, + bool a_bAddSignature = false + ) const + { + StringWriter writer(a_sBuffer); + return Save(writer, a_bAddSignature); + } + + /*-----------------------------------------------------------------------*/ + /** @} + @{ @name Accessing INI Data */ + + /** Retrieve all section names. The list is returned as an STL vector of + names and can be iterated or searched as necessary. Note that the + sort order of the returned strings is NOT DEFINED. You can sort + the names into the load order if desired. Search this file for ".sort" + for an example. + + NOTE! This structure contains only pointers to strings. The actual + string data is stored in memory owned by CSimpleIni. Ensure that the + CSimpleIni object is not destroyed or Reset() while these pointers + are in use! + + @param a_names Vector that will receive all of the section + names. See note above! + */ + void GetAllSections( + TNamesDepend & a_names + ) const; + + /** Retrieve all unique key names in a section. The sort order of the + returned strings is NOT DEFINED. You can sort the names into the load + order if desired. Search this file for ".sort" for an example. Only + unique key names are returned. + + NOTE! This structure contains only pointers to strings. The actual + string data is stored in memory owned by CSimpleIni. Ensure that the + CSimpleIni object is not destroyed or Reset() while these strings + are in use! + + @param a_pSection Section to request data for + @param a_names List that will receive all of the key + names. See note above! + + @return true Section was found. + @return false Matching section was not found. + */ + bool GetAllKeys( + const SI_CHAR * a_pSection, + TNamesDepend & a_names + ) const; + + /** Retrieve all values for a specific key. This method can be used when + multiple keys are both enabled and disabled. Note that the sort order + of the returned strings is NOT DEFINED. You can sort the names into + the load order if desired. Search this file for ".sort" for an example. + + NOTE! The returned values are pointers to string data stored in memory + owned by CSimpleIni. Ensure that the CSimpleIni object is not destroyed + or Reset while you are using this pointer! + + @param a_pSection Section to search + @param a_pKey Key to search for + @param a_values List to return if the key is not found + + @return true Key was found. + @return false Matching section/key was not found. + */ + bool GetAllValues( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + TNamesDepend & a_values + ) const; + + /** Query the number of keys in a specific section. Note that if multiple + keys are enabled, then this value may be different to the number of + keys returned by GetAllKeys. + + @param a_pSection Section to request data for + + @return -1 Section does not exist in the file + @return >=0 Number of keys in the section + */ + int GetSectionSize( + const SI_CHAR * a_pSection + ) const; + + /** Retrieve all key and value pairs for a section. The data is returned + as a pointer to an STL map and can be iterated or searched as + desired. Note that multiple entries for the same key may exist when + multiple keys have been enabled. + + NOTE! This structure contains only pointers to strings. The actual + string data is stored in memory owned by CSimpleIni. Ensure that the + CSimpleIni object is not destroyed or Reset() while these strings + are in use! + + @param a_pSection Name of the section to return + @return boolean Was a section matching the supplied + name found. + */ + const TKeyVal * GetSection( + const SI_CHAR * a_pSection + ) const; + + /** Retrieve the value for a specific key. If multiple keys are enabled + (see SetMultiKey) then only the first value associated with that key + will be returned, see GetAllValues for getting all values with multikey. + + NOTE! The returned value is a pointer to string data stored in memory + owned by CSimpleIni. Ensure that the CSimpleIni object is not destroyed + or Reset while you are using this pointer! + + @param a_pSection Section to search + @param a_pKey Key to search for + @param a_pDefault Value to return if the key is not found + @param a_pHasMultiple Optionally receive notification of if there are + multiple entries for this key. + + @return a_pDefault Key was not found in the section + @return other Value of the key + */ + const SI_CHAR * GetValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + const SI_CHAR * a_pDefault = NULL, + bool * a_pHasMultiple = NULL + ) const; + + /** Retrieve a numeric value for a specific key. If multiple keys are enabled + (see SetMultiKey) then only the first value associated with that key + will be returned, see GetAllValues for getting all values with multikey. + + @param a_pSection Section to search + @param a_pKey Key to search for + @param a_nDefault Value to return if the key is not found + @param a_pHasMultiple Optionally receive notification of if there are + multiple entries for this key. + + @return a_nDefault Key was not found in the section + @return other Value of the key + */ + long GetLongValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + long a_nDefault = 0, + bool * a_pHasMultiple = NULL + ) const; + + /** Retrieve a boolean value for a specific key. If multiple keys are enabled + (see SetMultiKey) then only the first value associated with that key + will be returned, see GetAllValues for getting all values with multikey. + + Strings starting with "t", "y", "on" or "1" are returned as logically true. + Strings starting with "f", "n", "of" or "0" are returned as logically false. + For all other values the default is returned. Character comparisons are + case-insensitive. + + @param a_pSection Section to search + @param a_pKey Key to search for + @param a_bDefault Value to return if the key is not found + @param a_pHasMultiple Optionally receive notification of if there are + multiple entries for this key. + + @return a_nDefault Key was not found in the section + @return other Value of the key + */ + bool GetBoolValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + bool a_bDefault = false, + bool * a_pHasMultiple = NULL + ) const; + + /** Add or update a section or value. This will always insert + when multiple keys are enabled. + + @param a_pSection Section to add or update + @param a_pKey Key to add or update. Set to NULL to + create an empty section. + @param a_pValue Value to set. Set to NULL to create an + empty section. + @param a_pComment Comment to be associated with the section or the + key. If a_pKey is NULL then it will be associated + with the section, otherwise the key. Note that a + comment may be set ONLY when the section or key is + first created (i.e. when this function returns the + value SI_INSERTED). If you wish to create a section + with a comment then you need to create the section + separately to the key. The comment string must be + in full comment form already (have a comment + character starting every line). + @param a_bForceReplace Should all existing values in a multi-key INI + file be replaced with this entry. This option has + no effect if not using multi-key files. The + difference between Delete/SetValue and SetValue + with a_bForceReplace = true, is that the load + order and comment will be preserved this way. + + @return SI_Error See error definitions + @return SI_UPDATED Value was updated + @return SI_INSERTED Value was inserted + */ + SI_Error SetValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + const SI_CHAR * a_pValue, + const SI_CHAR * a_pComment = NULL, + bool a_bForceReplace = false + ) + { + return AddEntry(a_pSection, a_pKey, a_pValue, a_pComment, a_bForceReplace, true); + } + + /** Add or update a numeric value. This will always insert + when multiple keys are enabled. + + @param a_pSection Section to add or update + @param a_pKey Key to add or update. + @param a_nValue Value to set. + @param a_pComment Comment to be associated with the key. See the + notes on SetValue() for comments. + @param a_bUseHex By default the value will be written to the file + in decimal format. Set this to true to write it + as hexadecimal. + @param a_bForceReplace Should all existing values in a multi-key INI + file be replaced with this entry. This option has + no effect if not using multi-key files. The + difference between Delete/SetLongValue and + SetLongValue with a_bForceReplace = true, is that + the load order and comment will be preserved this + way. + + @return SI_Error See error definitions + @return SI_UPDATED Value was updated + @return SI_INSERTED Value was inserted + */ + SI_Error SetLongValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + long a_nValue, + const SI_CHAR * a_pComment = NULL, + bool a_bUseHex = false, + bool a_bForceReplace = false + ); + + /** Add or update a boolean value. This will always insert + when multiple keys are enabled. + + @param a_pSection Section to add or update + @param a_pKey Key to add or update. + @param a_bValue Value to set. + @param a_pComment Comment to be associated with the key. See the + notes on SetValue() for comments. + @param a_bForceReplace Should all existing values in a multi-key INI + file be replaced with this entry. This option has + no effect if not using multi-key files. The + difference between Delete/SetBoolValue and + SetBoolValue with a_bForceReplace = true, is that + the load order and comment will be preserved this + way. + + @return SI_Error See error definitions + @return SI_UPDATED Value was updated + @return SI_INSERTED Value was inserted + */ + SI_Error SetBoolValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + bool a_bValue, + const SI_CHAR * a_pComment = NULL, + bool a_bForceReplace = false + ); + + /** Delete an entire section, or a key from a section. Note that the + data returned by GetSection is invalid and must not be used after + anything has been deleted from that section using this method. + Note when multiple keys is enabled, this will delete all keys with + that name; there is no way to selectively delete individual key/values + in this situation. + + @param a_pSection Section to delete key from, or if + a_pKey is NULL, the section to remove. + @param a_pKey Key to remove from the section. Set to + NULL to remove the entire section. + @param a_bRemoveEmpty If the section is empty after this key has + been deleted, should the empty section be + removed? + + @return true Key or section was deleted. + @return false Key or section was not found. + */ + bool Delete( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + bool a_bRemoveEmpty = false + ); + + /*-----------------------------------------------------------------------*/ + /** @} + @{ @name Converter */ + + /** Return a conversion object to convert text to the same encoding + as is used by the Save(), SaveFile() and SaveString() functions. + Use this to prepare the strings that you wish to append or prepend + to the output INI data. + */ + Converter GetConverter() const { + return Converter(m_bStoreIsUtf8); + } + + /*-----------------------------------------------------------------------*/ + /** @} */ + +private: + // copying is not permitted + CSimpleIniTempl(const CSimpleIniTempl &); // disabled + CSimpleIniTempl & operator=(const CSimpleIniTempl &); // disabled + + /** Parse the data looking for a file comment and store it if found. + */ + SI_Error FindFileComment( + SI_CHAR *& a_pData, + bool a_bCopyStrings + ); + + /** Parse the data looking for the next valid entry. The memory pointed to + by a_pData is modified by inserting NULL characters. The pointer is + updated to the current location in the block of text. + */ + bool FindEntry( + SI_CHAR *& a_pData, + const SI_CHAR *& a_pSection, + const SI_CHAR *& a_pKey, + const SI_CHAR *& a_pVal, + const SI_CHAR *& a_pComment + ) const; + + /** Add the section/key/value to our data. + + @param a_pSection Section name. Sections will be created if they + don't already exist. + @param a_pKey Key name. May be NULL to create an empty section. + Existing entries will be updated. New entries will + be created. + @param a_pValue Value for the key. + @param a_pComment Comment to be associated with the section or the + key. If a_pKey is NULL then it will be associated + with the section, otherwise the key. This must be + a string in full comment form already (have a + comment character starting every line). + @param a_bForceReplace Should all existing values in a multi-key INI + file be replaced with this entry. This option has + no effect if not using multi-key files. The + difference between Delete/AddEntry and AddEntry + with a_bForceReplace = true, is that the load + order and comment will be preserved this way. + @param a_bCopyStrings Should copies of the strings be made or not. + If false then the pointers will be used as is. + */ + SI_Error AddEntry( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + const SI_CHAR * a_pValue, + const SI_CHAR * a_pComment, + bool a_bForceReplace, + bool a_bCopyStrings + ); + + /** Is the supplied character a whitespace character? */ + inline bool IsSpace(SI_CHAR ch) const { + return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'); + } + + /** Does the supplied character start a comment line? */ + inline bool IsComment(SI_CHAR ch) const { + return (ch == ';' || ch == '#'); + } + + + /** Skip over a newline character (or characters) for either DOS or UNIX */ + inline void SkipNewLine(SI_CHAR *& a_pData) const { + a_pData += (*a_pData == '\r' && *(a_pData+1) == '\n') ? 2 : 1; + } + + /** Make a copy of the supplied string, replacing the original pointer */ + SI_Error CopyString(const SI_CHAR *& a_pString); + + /** Delete a string from the copied strings buffer if necessary */ + void DeleteString(const SI_CHAR * a_pString); + + /** Internal use of our string comparison function */ + bool IsLess(const SI_CHAR * a_pLeft, const SI_CHAR * a_pRight) const { + const static SI_STRLESS isLess = SI_STRLESS(); + return isLess(a_pLeft, a_pRight); + } + + bool IsMultiLineTag(const SI_CHAR * a_pData) const; + bool IsMultiLineData(const SI_CHAR * a_pData) const; + bool LoadMultiLineText( + SI_CHAR *& a_pData, + const SI_CHAR *& a_pVal, + const SI_CHAR * a_pTagName, + bool a_bAllowBlankLinesInComment = false + ) const; + bool IsNewLineChar(SI_CHAR a_c) const; + + bool OutputMultiLineText( + OutputWriter & a_oOutput, + Converter & a_oConverter, + const SI_CHAR * a_pText + ) const; + +private: + /** Copy of the INI file data in our character format. This will be + modified when parsed to have NULL characters added after all + interesting string entries. All of the string pointers to sections, + keys and values point into this block of memory. + */ + SI_CHAR * m_pData; + + /** Length of the data that we have stored. Used when deleting strings + to determine if the string is stored here or in the allocated string + buffer. + */ + size_t m_uDataLen; + + /** File comment for this data, if one exists. */ + const SI_CHAR * m_pFileComment; + + /** Parsed INI data. Section -> (Key -> Value). */ + TSection m_data; + + /** This vector stores allocated memory for copies of strings that have + been supplied after the file load. It will be empty unless SetValue() + has been called. + */ + TNamesDepend m_strings; + + /** Is the format of our datafile UTF-8 or MBCS? */ + bool m_bStoreIsUtf8; + + /** Are multiple values permitted for the same key? */ + bool m_bAllowMultiKey; + + /** Are data values permitted to span multiple lines? */ + bool m_bAllowMultiLine; + + /** Should spaces be written out surrounding the equals sign? */ + bool m_bSpaces; + + /** Next order value, used to ensure sections and keys are output in the + same order that they are loaded/added. + */ + int m_nOrder; +}; + +// --------------------------------------------------------------------------- +// IMPLEMENTATION +// --------------------------------------------------------------------------- + +template +CSimpleIniTempl::CSimpleIniTempl( + bool a_bIsUtf8, + bool a_bAllowMultiKey, + bool a_bAllowMultiLine + ) + : m_pData(0) + , m_uDataLen(0) + , m_pFileComment(NULL) + , m_bStoreIsUtf8(a_bIsUtf8) + , m_bAllowMultiKey(a_bAllowMultiKey) + , m_bAllowMultiLine(a_bAllowMultiLine) + , m_bSpaces(true) + , m_nOrder(0) +{ } + +template +CSimpleIniTempl::~CSimpleIniTempl() +{ + Reset(); +} + +template +void +CSimpleIniTempl::Reset() +{ + // remove all data + delete[] m_pData; + m_pData = NULL; + m_uDataLen = 0; + m_pFileComment = NULL; + if (!m_data.empty()) { + m_data.erase(m_data.begin(), m_data.end()); + } + + // remove all strings + if (!m_strings.empty()) { + typename TNamesDepend::iterator i = m_strings.begin(); + for (; i != m_strings.end(); ++i) { + delete[] const_cast(i->pItem); + } + m_strings.erase(m_strings.begin(), m_strings.end()); + } +} + +template +SI_Error +CSimpleIniTempl::LoadFile( + const char * a_pszFile + ) +{ + FILE * fp = NULL; +#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE + fopen_s(&fp, a_pszFile, "rb"); +#else // !__STDC_WANT_SECURE_LIB__ + fp = fopen(a_pszFile, "rb"); +#endif // __STDC_WANT_SECURE_LIB__ + if (!fp) { + return SI_FILE; + } + SI_Error rc = LoadFile(fp); + fclose(fp); + return rc; +} + +#ifdef SI_HAS_WIDE_FILE +template +SI_Error +CSimpleIniTempl::LoadFile( + const SI_WCHAR_T * a_pwszFile + ) +{ +#ifdef _WIN32 + FILE * fp = NULL; +#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE + _wfopen_s(&fp, a_pwszFile, L"rb"); +#else // !__STDC_WANT_SECURE_LIB__ + fp = _wfopen(a_pwszFile, L"rb"); +#endif // __STDC_WANT_SECURE_LIB__ + if (!fp) return SI_FILE; + SI_Error rc = LoadFile(fp); + fclose(fp); + return rc; +#else // !_WIN32 (therefore SI_CONVERT_ICU) + char szFile[256]; + u_austrncpy(szFile, a_pwszFile, sizeof(szFile)); + return LoadFile(szFile); +#endif // _WIN32 +} +#endif // SI_HAS_WIDE_FILE + +template +SI_Error +CSimpleIniTempl::LoadFile( + FILE * a_fpFile + ) +{ + // load the raw file data + int retval = fseek(a_fpFile, 0, SEEK_END); + if (retval != 0) { + return SI_FILE; + } + long lSize = ftell(a_fpFile); + if (lSize < 0) { + return SI_FILE; + } + if (lSize == 0) { + return SI_OK; + } + char * pData = new char[lSize]; + if (!pData) { + return SI_NOMEM; + } + fseek(a_fpFile, 0, SEEK_SET); + size_t uRead = fread(pData, sizeof(char), lSize, a_fpFile); + if (uRead != (size_t) lSize) { + delete[] pData; + return SI_FILE; + } + + // convert the raw data to unicode + SI_Error rc = Load(pData, uRead); + delete[] pData; + return rc; +} + +template +SI_Error +CSimpleIniTempl::Load( + const char * a_pData, + size_t a_uDataLen + ) +{ + SI_CONVERTER converter(m_bStoreIsUtf8); + + if (a_uDataLen == 0) { + return SI_OK; + } + + // consume the UTF-8 BOM if it exists + if (m_bStoreIsUtf8 && a_uDataLen >= 3) { + if (memcmp(a_pData, SI_UTF8_SIGNATURE, 3) == 0) { + a_pData += 3; + a_uDataLen -= 3; + } + } + + // determine the length of the converted data + size_t uLen = converter.SizeFromStore(a_pData, a_uDataLen); + if (uLen == (size_t)(-1)) { + return SI_FAIL; + } + + // allocate memory for the data, ensure that there is a NULL + // terminator wherever the converted data ends + SI_CHAR * pData = new SI_CHAR[uLen+1]; + if (!pData) { + return SI_NOMEM; + } + memset(pData, 0, sizeof(SI_CHAR)*(uLen+1)); + + // convert the data + if (!converter.ConvertFromStore(a_pData, a_uDataLen, pData, uLen)) { + delete[] pData; + return SI_FAIL; + } + + // parse it + const static SI_CHAR empty = 0; + SI_CHAR * pWork = pData; + const SI_CHAR * pSection = ∅ + const SI_CHAR * pItem = NULL; + const SI_CHAR * pVal = NULL; + const SI_CHAR * pComment = NULL; + + // We copy the strings if we are loading data into this class when we + // already have stored some. + bool bCopyStrings = (m_pData != NULL); + + // find a file comment if it exists, this is a comment that starts at the + // beginning of the file and continues until the first blank line. + SI_Error rc = FindFileComment(pWork, bCopyStrings); + if (rc < 0) return rc; + + // add every entry in the file to the data table + while (FindEntry(pWork, pSection, pItem, pVal, pComment)) { + rc = AddEntry(pSection, pItem, pVal, pComment, false, bCopyStrings); + if (rc < 0) return rc; + } + + // store these strings if we didn't copy them + if (bCopyStrings) { + delete[] pData; + } + else { + m_pData = pData; + m_uDataLen = uLen+1; + } + + return SI_OK; +} + +#ifdef SI_SUPPORT_IOSTREAMS +template +SI_Error +CSimpleIniTempl::Load( + std::istream & a_istream + ) +{ + std::string strData; + char szBuf[512]; + do { + a_istream.get(szBuf, sizeof(szBuf), '\0'); + strData.append(szBuf); + } + while (a_istream.good()); + return Load(strData); +} +#endif // SI_SUPPORT_IOSTREAMS + +template +SI_Error +CSimpleIniTempl::FindFileComment( + SI_CHAR *& a_pData, + bool a_bCopyStrings + ) +{ + // there can only be a single file comment + if (m_pFileComment) { + return SI_OK; + } + + // Load the file comment as multi-line text, this will modify all of + // the newline characters to be single \n chars + if (!LoadMultiLineText(a_pData, m_pFileComment, NULL, false)) { + return SI_OK; + } + + // copy the string if necessary + if (a_bCopyStrings) { + SI_Error rc = CopyString(m_pFileComment); + if (rc < 0) return rc; + } + + return SI_OK; +} + +template +bool +CSimpleIniTempl::FindEntry( + SI_CHAR *& a_pData, + const SI_CHAR *& a_pSection, + const SI_CHAR *& a_pKey, + const SI_CHAR *& a_pVal, + const SI_CHAR *& a_pComment + ) const +{ + a_pComment = NULL; + + SI_CHAR * pTrail = NULL; + while (*a_pData) { + // skip spaces and empty lines + while (*a_pData && IsSpace(*a_pData)) { + ++a_pData; + } + if (!*a_pData) { + break; + } + + // skip processing of comment lines but keep a pointer to + // the start of the comment. + if (IsComment(*a_pData)) { + LoadMultiLineText(a_pData, a_pComment, NULL, true); + continue; + } + + // process section names + if (*a_pData == '[') { + // skip leading spaces + ++a_pData; + while (*a_pData && IsSpace(*a_pData)) { + ++a_pData; + } + + // find the end of the section name (it may contain spaces) + // and convert it to lowercase as necessary + a_pSection = a_pData; + while (*a_pData && *a_pData != ']' && !IsNewLineChar(*a_pData)) { + ++a_pData; + } + + // if it's an invalid line, just skip it + if (*a_pData != ']') { + continue; + } + + // remove trailing spaces from the section + pTrail = a_pData - 1; + while (pTrail >= a_pSection && IsSpace(*pTrail)) { + --pTrail; + } + ++pTrail; + *pTrail = 0; + + // skip to the end of the line + ++a_pData; // safe as checked that it == ']' above + while (*a_pData && !IsNewLineChar(*a_pData)) { + ++a_pData; + } + + a_pKey = NULL; + a_pVal = NULL; + return true; + } + + // find the end of the key name (it may contain spaces) + // and convert it to lowercase as necessary + a_pKey = a_pData; + while (*a_pData && *a_pData != '=' && !IsNewLineChar(*a_pData)) { + ++a_pData; + } + + // if it's an invalid line, just skip it + if (*a_pData != '=') { + continue; + } + + // empty keys are invalid + if (a_pKey == a_pData) { + while (*a_pData && !IsNewLineChar(*a_pData)) { + ++a_pData; + } + continue; + } + + // remove trailing spaces from the key + pTrail = a_pData - 1; + while (pTrail >= a_pKey && IsSpace(*pTrail)) { + --pTrail; + } + ++pTrail; + *pTrail = 0; + + // skip leading whitespace on the value + ++a_pData; // safe as checked that it == '=' above + while (*a_pData && !IsNewLineChar(*a_pData) && IsSpace(*a_pData)) { + ++a_pData; + } + + // find the end of the value which is the end of this line + a_pVal = a_pData; + while (*a_pData && !IsNewLineChar(*a_pData)) { + ++a_pData; + } + + // remove trailing spaces from the value + pTrail = a_pData - 1; + if (*a_pData) { // prepare for the next round + SkipNewLine(a_pData); + } + while (pTrail >= a_pVal && IsSpace(*pTrail)) { + --pTrail; + } + ++pTrail; + *pTrail = 0; + + // check for multi-line entries + if (m_bAllowMultiLine && IsMultiLineTag(a_pVal)) { + // skip the "<<<" to get the tag that will end the multiline + const SI_CHAR * pTagName = a_pVal + 3; + return LoadMultiLineText(a_pData, a_pVal, pTagName); + } + + // return the standard entry + return true; + } + + return false; +} + +template +bool +CSimpleIniTempl::IsMultiLineTag( + const SI_CHAR * a_pVal + ) const +{ + // check for the "<<<" prefix for a multi-line entry + if (*a_pVal++ != '<') return false; + if (*a_pVal++ != '<') return false; + if (*a_pVal++ != '<') return false; + return true; +} + +template +bool +CSimpleIniTempl::IsMultiLineData( + const SI_CHAR * a_pData + ) const +{ + // data is multi-line if it has any of the following features: + // * whitespace prefix + // * embedded newlines + // * whitespace suffix + + // empty string + if (!*a_pData) { + return false; + } + + // check for prefix + if (IsSpace(*a_pData)) { + return true; + } + + // embedded newlines + while (*a_pData) { + if (IsNewLineChar(*a_pData)) { + return true; + } + ++a_pData; + } + + // check for suffix + if (IsSpace(*--a_pData)) { + return true; + } + + return false; +} + +template +bool +CSimpleIniTempl::IsNewLineChar( + SI_CHAR a_c + ) const +{ + return (a_c == '\n' || a_c == '\r'); +} + +template +bool +CSimpleIniTempl::LoadMultiLineText( + SI_CHAR *& a_pData, + const SI_CHAR *& a_pVal, + const SI_CHAR * a_pTagName, + bool a_bAllowBlankLinesInComment + ) const +{ + // we modify this data to strip all newlines down to a single '\n' + // character. This means that on Windows we need to strip out some + // characters which will make the data shorter. + // i.e. LINE1-LINE1\r\nLINE2-LINE2\0 will become + // LINE1-LINE1\nLINE2-LINE2\0 + // The pDataLine entry is the pointer to the location in memory that + // the current line needs to start to run following the existing one. + // This may be the same as pCurrLine in which case no move is needed. + SI_CHAR * pDataLine = a_pData; + SI_CHAR * pCurrLine; + + // value starts at the current line + a_pVal = a_pData; + + // find the end tag. This tag must start in column 1 and be + // followed by a newline. No whitespace removal is done while + // searching for this tag. + SI_CHAR cEndOfLineChar = *a_pData; + for(;;) { + // if we are loading comments then we need a comment character as + // the first character on every line + if (!a_pTagName && !IsComment(*a_pData)) { + // if we aren't allowing blank lines then we're done + if (!a_bAllowBlankLinesInComment) { + break; + } + + // if we are allowing blank lines then we only include them + // in this comment if another comment follows, so read ahead + // to find out. + SI_CHAR * pCurr = a_pData; + int nNewLines = 0; + while (IsSpace(*pCurr)) { + if (IsNewLineChar(*pCurr)) { + ++nNewLines; + SkipNewLine(pCurr); + } + else { + ++pCurr; + } + } + + // we have a comment, add the blank lines to the output + // and continue processing from here + if (IsComment(*pCurr)) { + for (; nNewLines > 0; --nNewLines) *pDataLine++ = '\n'; + a_pData = pCurr; + continue; + } + + // the comment ends here + break; + } + + // find the end of this line + pCurrLine = a_pData; + while (*a_pData && !IsNewLineChar(*a_pData)) ++a_pData; + + // move this line down to the location that it should be if necessary + if (pDataLine < pCurrLine) { + size_t nLen = (size_t) (a_pData - pCurrLine); + memmove(pDataLine, pCurrLine, nLen * sizeof(SI_CHAR)); + pDataLine[nLen] = '\0'; + } + + // end the line with a NULL + cEndOfLineChar = *a_pData; + *a_pData = 0; + + // if are looking for a tag then do the check now. This is done before + // checking for end of the data, so that if we have the tag at the end + // of the data then the tag is removed correctly. + if (a_pTagName && + (!IsLess(pDataLine, a_pTagName) && !IsLess(a_pTagName, pDataLine))) + { + break; + } + + // if we are at the end of the data then we just automatically end + // this entry and return the current data. + if (!cEndOfLineChar) { + return true; + } + + // otherwise we need to process this newline to ensure that it consists + // of just a single \n character. + pDataLine += (a_pData - pCurrLine); + *a_pData = cEndOfLineChar; + SkipNewLine(a_pData); + *pDataLine++ = '\n'; + } + + // if we didn't find a comment at all then return false + if (a_pVal == a_pData) { + a_pVal = NULL; + return false; + } + + // the data (which ends at the end of the last line) needs to be + // null-terminated BEFORE before the newline character(s). If the + // user wants a new line in the multi-line data then they need to + // add an empty line before the tag. + *--pDataLine = '\0'; + + // if looking for a tag and if we aren't at the end of the data, + // then move a_pData to the start of the next line. + if (a_pTagName && cEndOfLineChar) { + SI_ASSERT(IsNewLineChar(cEndOfLineChar)); + *a_pData = cEndOfLineChar; + SkipNewLine(a_pData); + } + + return true; +} + +template +SI_Error +CSimpleIniTempl::CopyString( + const SI_CHAR *& a_pString + ) +{ + size_t uLen = 0; + if (sizeof(SI_CHAR) == sizeof(char)) { + uLen = strlen((const char *)a_pString); + } + else if (sizeof(SI_CHAR) == sizeof(wchar_t)) { + uLen = wcslen((const wchar_t *)a_pString); + } + else { + for ( ; a_pString[uLen]; ++uLen) /*loop*/ ; + } + ++uLen; // NULL character + SI_CHAR * pCopy = new SI_CHAR[uLen]; + if (!pCopy) { + return SI_NOMEM; + } + memcpy(pCopy, a_pString, sizeof(SI_CHAR)*uLen); + m_strings.push_back(pCopy); + a_pString = pCopy; + return SI_OK; +} + +template +SI_Error +CSimpleIniTempl::AddEntry( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + const SI_CHAR * a_pValue, + const SI_CHAR * a_pComment, + bool a_bForceReplace, + bool a_bCopyStrings + ) +{ + SI_Error rc; + bool bInserted = false; + + SI_ASSERT(!a_pComment || IsComment(*a_pComment)); + + // if we are copying strings then make a copy of the comment now + // because we will need it when we add the entry. + if (a_bCopyStrings && a_pComment) { + rc = CopyString(a_pComment); + if (rc < 0) return rc; + } + + // create the section entry if necessary + typename TSection::iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + // if the section doesn't exist then we need a copy as the + // string needs to last beyond the end of this function + if (a_bCopyStrings) { + rc = CopyString(a_pSection); + if (rc < 0) return rc; + } + + // only set the comment if this is a section only entry + Entry oSection(a_pSection, ++m_nOrder); + if (a_pComment && (!a_pKey || !a_pValue)) { + oSection.pComment = a_pComment; + } + + typename TSection::value_type oEntry(oSection, TKeyVal()); + typedef typename TSection::iterator SectionIterator; + std::pair i = m_data.insert(oEntry); + iSection = i.first; + bInserted = true; + } + if (!a_pKey || !a_pValue) { + // section only entries are specified with pItem and pVal as NULL + return bInserted ? SI_INSERTED : SI_UPDATED; + } + + // check for existence of the key + TKeyVal & keyval = iSection->second; + typename TKeyVal::iterator iKey = keyval.find(a_pKey); + + // remove all existing entries but save the load order and + // comment of the first entry + int nLoadOrder = ++m_nOrder; + if (iKey != keyval.end() && m_bAllowMultiKey && a_bForceReplace) { + const SI_CHAR * pComment = NULL; + while (iKey != keyval.end() && !IsLess(a_pKey, iKey->first.pItem)) { + if (iKey->first.nOrder < nLoadOrder) { + nLoadOrder = iKey->first.nOrder; + pComment = iKey->first.pComment; + } + ++iKey; + } + if (pComment) { + DeleteString(a_pComment); + a_pComment = pComment; + CopyString(a_pComment); + } + Delete(a_pSection, a_pKey); + iKey = keyval.end(); + } + + // make string copies if necessary + bool bForceCreateNewKey = m_bAllowMultiKey && !a_bForceReplace; + if (a_bCopyStrings) { + if (bForceCreateNewKey || iKey == keyval.end()) { + // if the key doesn't exist then we need a copy as the + // string needs to last beyond the end of this function + // because we will be inserting the key next + rc = CopyString(a_pKey); + if (rc < 0) return rc; + } + + // we always need a copy of the value + rc = CopyString(a_pValue); + if (rc < 0) return rc; + } + + // create the key entry + if (iKey == keyval.end() || bForceCreateNewKey) { + Entry oKey(a_pKey, nLoadOrder); + if (a_pComment) { + oKey.pComment = a_pComment; + } + typename TKeyVal::value_type oEntry(oKey, NULL); + iKey = keyval.insert(oEntry); + bInserted = true; + } + iKey->second = a_pValue; + return bInserted ? SI_INSERTED : SI_UPDATED; +} + +template +const SI_CHAR * +CSimpleIniTempl::GetValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + const SI_CHAR * a_pDefault, + bool * a_pHasMultiple + ) const +{ + if (a_pHasMultiple) { + *a_pHasMultiple = false; + } + if (!a_pSection || !a_pKey) { + return a_pDefault; + } + typename TSection::const_iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return a_pDefault; + } + typename TKeyVal::const_iterator iKeyVal = iSection->second.find(a_pKey); + if (iKeyVal == iSection->second.end()) { + return a_pDefault; + } + + // check for multiple entries with the same key + if (m_bAllowMultiKey && a_pHasMultiple) { + typename TKeyVal::const_iterator iTemp = iKeyVal; + if (++iTemp != iSection->second.end()) { + if (!IsLess(a_pKey, iTemp->first.pItem)) { + *a_pHasMultiple = true; + } + } + } + + return iKeyVal->second; +} + +template +long +CSimpleIniTempl::GetLongValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + long a_nDefault, + bool * a_pHasMultiple + ) const +{ + // return the default if we don't have a value + const SI_CHAR * pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); + if (!pszValue || !*pszValue) return a_nDefault; + + // convert to UTF-8/MBCS which for a numeric value will be the same as ASCII + char szValue[64] = { 0 }; + SI_CONVERTER c(m_bStoreIsUtf8); + if (!c.ConvertToStore(pszValue, szValue, sizeof(szValue))) { + return a_nDefault; + } + + // handle the value as hex if prefaced with "0x" + long nValue = a_nDefault; + char * pszSuffix = szValue; + if (szValue[0] == '0' && (szValue[1] == 'x' || szValue[1] == 'X')) { + if (!szValue[2]) return a_nDefault; + nValue = strtol(&szValue[2], &pszSuffix, 16); + } + else { + nValue = strtol(szValue, &pszSuffix, 10); + } + + // any invalid strings will return the default value + if (*pszSuffix) { + return a_nDefault; + } + + return nValue; +} + +template +SI_Error +CSimpleIniTempl::SetLongValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + long a_nValue, + const SI_CHAR * a_pComment, + bool a_bUseHex, + bool a_bForceReplace + ) +{ + // use SetValue to create sections + if (!a_pSection || !a_pKey) return SI_FAIL; + + // convert to an ASCII string + char szInput[64]; +#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE + sprintf_s(szInput, a_bUseHex ? "0x%lx" : "%ld", a_nValue); +#else // !__STDC_WANT_SECURE_LIB__ + sprintf(szInput, a_bUseHex ? "0x%lx" : "%ld", a_nValue); +#endif // __STDC_WANT_SECURE_LIB__ + + // convert to output text + SI_CHAR szOutput[64]; + SI_CONVERTER c(m_bStoreIsUtf8); + c.ConvertFromStore(szInput, strlen(szInput) + 1, + szOutput, sizeof(szOutput) / sizeof(SI_CHAR)); + + // actually add it + return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); +} + +template +bool +CSimpleIniTempl::GetBoolValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + bool a_bDefault, + bool * a_pHasMultiple + ) const +{ + // return the default if we don't have a value + const SI_CHAR * pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); + if (!pszValue || !*pszValue) return a_bDefault; + + // we only look at the minimum number of characters + switch (pszValue[0]) { + case 't': case 'T': // true + case 'y': case 'Y': // yes + case '1': // 1 (one) + return true; + + case 'f': case 'F': // false + case 'n': case 'N': // no + case '0': // 0 (zero) + return false; + + case 'o': case 'O': + if (pszValue[1] == 'n' || pszValue[1] == 'N') return true; // on + if (pszValue[1] == 'f' || pszValue[1] == 'F') return false; // off + break; + } + + // no recognized value, return the default + return a_bDefault; +} + +template +SI_Error +CSimpleIniTempl::SetBoolValue( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + bool a_bValue, + const SI_CHAR * a_pComment, + bool a_bForceReplace + ) +{ + // use SetValue to create sections + if (!a_pSection || !a_pKey) return SI_FAIL; + + // convert to an ASCII string + const char * pszInput = a_bValue ? "true" : "false"; + + // convert to output text + SI_CHAR szOutput[64]; + SI_CONVERTER c(m_bStoreIsUtf8); + c.ConvertFromStore(pszInput, strlen(pszInput) + 1, + szOutput, sizeof(szOutput) / sizeof(SI_CHAR)); + + // actually add it + return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); +} + +template +bool +CSimpleIniTempl::GetAllValues( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + TNamesDepend & a_values + ) const +{ + a_values.clear(); + + if (!a_pSection || !a_pKey) { + return false; + } + typename TSection::const_iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return false; + } + typename TKeyVal::const_iterator iKeyVal = iSection->second.find(a_pKey); + if (iKeyVal == iSection->second.end()) { + return false; + } + + // insert all values for this key + a_values.push_back(Entry(iKeyVal->second, iKeyVal->first.pComment, iKeyVal->first.nOrder)); + if (m_bAllowMultiKey) { + ++iKeyVal; + while (iKeyVal != iSection->second.end() && !IsLess(a_pKey, iKeyVal->first.pItem)) { + a_values.push_back(Entry(iKeyVal->second, iKeyVal->first.pComment, iKeyVal->first.nOrder)); + ++iKeyVal; + } + } + + return true; +} + +template +int +CSimpleIniTempl::GetSectionSize( + const SI_CHAR * a_pSection + ) const +{ + if (!a_pSection) { + return -1; + } + + typename TSection::const_iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return -1; + } + const TKeyVal & section = iSection->second; + + // if multi-key isn't permitted then the section size is + // the number of keys that we have. + if (!m_bAllowMultiKey || section.empty()) { + return (int) section.size(); + } + + // otherwise we need to count them + int nCount = 0; + const SI_CHAR * pLastKey = NULL; + typename TKeyVal::const_iterator iKeyVal = section.begin(); + for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n) { + if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { + ++nCount; + pLastKey = iKeyVal->first.pItem; + } + } + return nCount; +} + +template +const typename CSimpleIniTempl::TKeyVal * +CSimpleIniTempl::GetSection( + const SI_CHAR * a_pSection + ) const +{ + if (a_pSection) { + typename TSection::const_iterator i = m_data.find(a_pSection); + if (i != m_data.end()) { + return &(i->second); + } + } + return 0; +} + +template +void +CSimpleIniTempl::GetAllSections( + TNamesDepend & a_names + ) const +{ + a_names.clear(); + typename TSection::const_iterator i = m_data.begin(); + for (int n = 0; i != m_data.end(); ++i, ++n ) { + a_names.push_back(i->first); + } +} + +template +bool +CSimpleIniTempl::GetAllKeys( + const SI_CHAR * a_pSection, + TNamesDepend & a_names + ) const +{ + a_names.clear(); + + if (!a_pSection) { + return false; + } + + typename TSection::const_iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return false; + } + + const TKeyVal & section = iSection->second; + const SI_CHAR * pLastKey = NULL; + typename TKeyVal::const_iterator iKeyVal = section.begin(); + for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n ) { + if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { + a_names.push_back(iKeyVal->first); + pLastKey = iKeyVal->first.pItem; + } + } + + return true; +} + +template +SI_Error +CSimpleIniTempl::SaveFile( + const char * a_pszFile, + bool a_bAddSignature + ) const +{ + FILE * fp = NULL; +#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE + fopen_s(&fp, a_pszFile, "wb"); +#else // !__STDC_WANT_SECURE_LIB__ + fp = fopen(a_pszFile, "wb"); +#endif // __STDC_WANT_SECURE_LIB__ + if (!fp) return SI_FILE; + SI_Error rc = SaveFile(fp, a_bAddSignature); + fclose(fp); + return rc; +} + +#ifdef SI_HAS_WIDE_FILE +template +SI_Error +CSimpleIniTempl::SaveFile( + const SI_WCHAR_T * a_pwszFile, + bool a_bAddSignature + ) const +{ +#ifdef _WIN32 + FILE * fp = NULL; +#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE + _wfopen_s(&fp, a_pwszFile, L"wb"); +#else // !__STDC_WANT_SECURE_LIB__ + fp = _wfopen(a_pwszFile, L"wb"); +#endif // __STDC_WANT_SECURE_LIB__ + if (!fp) return SI_FILE; + SI_Error rc = SaveFile(fp, a_bAddSignature); + fclose(fp); + return rc; +#else // !_WIN32 (therefore SI_CONVERT_ICU) + char szFile[256]; + u_austrncpy(szFile, a_pwszFile, sizeof(szFile)); + return SaveFile(szFile, a_bAddSignature); +#endif // _WIN32 +} +#endif // SI_HAS_WIDE_FILE + +template +SI_Error +CSimpleIniTempl::SaveFile( + FILE * a_pFile, + bool a_bAddSignature + ) const +{ + FileWriter writer(a_pFile); + return Save(writer, a_bAddSignature); +} + +template +SI_Error +CSimpleIniTempl::Save( + OutputWriter & a_oOutput, + bool a_bAddSignature + ) const +{ + Converter convert(m_bStoreIsUtf8); + + // add the UTF-8 signature if it is desired + if (m_bStoreIsUtf8 && a_bAddSignature) { + a_oOutput.Write(SI_UTF8_SIGNATURE); + } + + // get all of the sections sorted in load order + TNamesDepend oSections; + GetAllSections(oSections); +#if defined(_MSC_VER) && _MSC_VER <= 1200 + oSections.sort(); +#elif defined(__BORLANDC__) + oSections.sort(Entry::LoadOrder()); +#else + oSections.sort(typename Entry::LoadOrder()); +#endif + + // write the file comment if we have one + bool bNeedNewLine = false; + if (m_pFileComment) { + if (!OutputMultiLineText(a_oOutput, convert, m_pFileComment)) { + return SI_FAIL; + } + bNeedNewLine = true; + } + + // iterate through our sections and output the data + typename TNamesDepend::const_iterator iSection = oSections.begin(); + for ( ; iSection != oSections.end(); ++iSection ) { + // write out the comment if there is one + if (iSection->pComment) { + if (bNeedNewLine) { + a_oOutput.Write(SI_NEWLINE_A); + a_oOutput.Write(SI_NEWLINE_A); + } + if (!OutputMultiLineText(a_oOutput, convert, iSection->pComment)) { + return SI_FAIL; + } + bNeedNewLine = false; + } + + if (bNeedNewLine) { + a_oOutput.Write(SI_NEWLINE_A); + a_oOutput.Write(SI_NEWLINE_A); + bNeedNewLine = false; + } + + // write the section (unless there is no section name) + if (*iSection->pItem) { + if (!convert.ConvertToStore(iSection->pItem)) { + return SI_FAIL; + } + a_oOutput.Write("["); + a_oOutput.Write(convert.Data()); + a_oOutput.Write("]"); + a_oOutput.Write(SI_NEWLINE_A); + } + + // get all of the keys sorted in load order + TNamesDepend oKeys; + GetAllKeys(iSection->pItem, oKeys); +#if defined(_MSC_VER) && _MSC_VER <= 1200 + oKeys.sort(); +#elif defined(__BORLANDC__) + oKeys.sort(Entry::LoadOrder()); +#else + oKeys.sort(typename Entry::LoadOrder()); +#endif + + // write all keys and values + typename TNamesDepend::const_iterator iKey = oKeys.begin(); + for ( ; iKey != oKeys.end(); ++iKey) { + // get all values for this key + TNamesDepend oValues; + GetAllValues(iSection->pItem, iKey->pItem, oValues); + + typename TNamesDepend::const_iterator iValue = oValues.begin(); + for ( ; iValue != oValues.end(); ++iValue) { + // write out the comment if there is one + if (iValue->pComment) { + a_oOutput.Write(SI_NEWLINE_A); + if (!OutputMultiLineText(a_oOutput, convert, iValue->pComment)) { + return SI_FAIL; + } + } + + // write the key + if (!convert.ConvertToStore(iKey->pItem)) { + return SI_FAIL; + } + a_oOutput.Write(convert.Data()); + + // write the value + if (!convert.ConvertToStore(iValue->pItem)) { + return SI_FAIL; + } + a_oOutput.Write(m_bSpaces ? " = " : "="); + if (m_bAllowMultiLine && IsMultiLineData(iValue->pItem)) { + // multi-line data needs to be processed specially to ensure + // that we use the correct newline format for the current system + a_oOutput.Write("<<pItem)) { + return SI_FAIL; + } + a_oOutput.Write("SI-END-OF-MULTILINE-TEXT"); + } + else { + a_oOutput.Write(convert.Data()); + } + a_oOutput.Write(SI_NEWLINE_A); + } + } + + bNeedNewLine = true; + } + + return SI_OK; +} + +template +bool +CSimpleIniTempl::OutputMultiLineText( + OutputWriter & a_oOutput, + Converter & a_oConverter, + const SI_CHAR * a_pText + ) const +{ + const SI_CHAR * pEndOfLine; + SI_CHAR cEndOfLineChar = *a_pText; + while (cEndOfLineChar) { + // find the end of this line + pEndOfLine = a_pText; + for (; *pEndOfLine && *pEndOfLine != '\n'; ++pEndOfLine) /*loop*/ ; + cEndOfLineChar = *pEndOfLine; + + // temporarily null terminate, convert and output the line + *const_cast(pEndOfLine) = 0; + if (!a_oConverter.ConvertToStore(a_pText)) { + return false; + } + *const_cast(pEndOfLine) = cEndOfLineChar; + a_pText += (pEndOfLine - a_pText) + 1; + a_oOutput.Write(a_oConverter.Data()); + a_oOutput.Write(SI_NEWLINE_A); + } + return true; +} + +template +bool +CSimpleIniTempl::Delete( + const SI_CHAR * a_pSection, + const SI_CHAR * a_pKey, + bool a_bRemoveEmpty + ) +{ + if (!a_pSection) { + return false; + } + + typename TSection::iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return false; + } + + // remove a single key if we have a keyname + if (a_pKey) { + typename TKeyVal::iterator iKeyVal = iSection->second.find(a_pKey); + if (iKeyVal == iSection->second.end()) { + return false; + } + + // remove any copied strings and then the key + typename TKeyVal::iterator iDelete; + do { + iDelete = iKeyVal++; + + DeleteString(iDelete->first.pItem); + DeleteString(iDelete->second); + iSection->second.erase(iDelete); + } + while (iKeyVal != iSection->second.end() + && !IsLess(a_pKey, iKeyVal->first.pItem)); + + // done now if the section is not empty or we are not pruning away + // the empty sections. Otherwise let it fall through into the section + // deletion code + if (!a_bRemoveEmpty || !iSection->second.empty()) { + return true; + } + } + else { + // delete all copied strings from this section. The actual + // entries will be removed when the section is removed. + typename TKeyVal::iterator iKeyVal = iSection->second.begin(); + for ( ; iKeyVal != iSection->second.end(); ++iKeyVal) { + DeleteString(iKeyVal->first.pItem); + DeleteString(iKeyVal->second); + } + } + + // delete the section itself + DeleteString(iSection->first.pItem); + m_data.erase(iSection); + + return true; +} + +template +void +CSimpleIniTempl::DeleteString( + const SI_CHAR * a_pString + ) +{ + // strings may exist either inside the data block, or they will be + // individually allocated and stored in m_strings. We only physically + // delete those stored in m_strings. + if (a_pString < m_pData || a_pString >= m_pData + m_uDataLen) { + typename TNamesDepend::iterator i = m_strings.begin(); + for (;i != m_strings.end(); ++i) { + if (a_pString == i->pItem) { + delete[] const_cast(i->pItem); + m_strings.erase(i); + break; + } + } + } +} + +// --------------------------------------------------------------------------- +// CONVERSION FUNCTIONS +// --------------------------------------------------------------------------- + +// Defines the conversion classes for different libraries. Before including +// SimpleIni.h, set the converter that you wish you use by defining one of the +// following symbols. +// +// SI_CONVERT_GENERIC Use the Unicode reference conversion library in +// the accompanying files ConvertUTF.h/c +// SI_CONVERT_ICU Use the IBM ICU conversion library. Requires +// ICU headers on include path and icuuc.lib +// SI_CONVERT_WIN32 Use the Win32 API functions for conversion. + +#if !defined(SI_CONVERT_GENERIC) && !defined(SI_CONVERT_WIN32) && !defined(SI_CONVERT_ICU) +# ifdef _WIN32 +# define SI_CONVERT_WIN32 +# else +# define SI_CONVERT_GENERIC +# endif +#endif + +/** + * Generic case-sensitive less than comparison. This class returns numerically + * ordered ASCII case-sensitive text for all possible sizes and types of + * SI_CHAR. + */ +template +struct SI_GenericCase { + bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { + long cmp; + for ( ;*pLeft && *pRight; ++pLeft, ++pRight) { + cmp = (long) *pLeft - (long) *pRight; + if (cmp != 0) { + return cmp < 0; + } + } + return *pRight != 0; + } +}; + +/** + * Generic ASCII case-insensitive less than comparison. This class returns + * numerically ordered ASCII case-insensitive text for all possible sizes + * and types of SI_CHAR. It is not safe for MBCS text comparison where + * ASCII A-Z characters are used in the encoding of multi-byte characters. + */ +template +struct SI_GenericNoCase { + inline SI_CHAR locase(SI_CHAR ch) const { + return (ch < 'A' || ch > 'Z') ? ch : (ch - 'A' + 'a'); + } + bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { + long cmp; + for ( ;*pLeft && *pRight; ++pLeft, ++pRight) { + cmp = (long) locase(*pLeft) - (long) locase(*pRight); + if (cmp != 0) { + return cmp < 0; + } + } + return *pRight != 0; + } +}; + +/** + * Null conversion class for MBCS/UTF-8 to char (or equivalent). + */ +template +class SI_ConvertA { + bool m_bStoreIsUtf8; +protected: + SI_ConvertA() { } +public: + SI_ConvertA(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) { } + + /* copy and assignment */ + SI_ConvertA(const SI_ConvertA & rhs) { operator=(rhs); } + SI_ConvertA & operator=(const SI_ConvertA & rhs) { + m_bStoreIsUtf8 = rhs.m_bStoreIsUtf8; + return *this; + } + + /** Calculate the number of SI_CHAR required for converting the input + * from the storage format. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to SI_CHAR. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @return Number of SI_CHAR required by the string when + * converted. If there are embedded NULL bytes in the + * input data, only the string up and not including + * the NULL byte will be converted. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeFromStore( + const char * a_pInputData, + size_t a_uInputDataLen) + { + (void)a_pInputData; + SI_ASSERT(a_uInputDataLen != (size_t) -1); + + // ASCII/MBCS/UTF-8 needs no conversion + return a_uInputDataLen; + } + + /** Convert the input string from the storage format to SI_CHAR. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to SI_CHAR. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @param a_pOutputData Pointer to the output buffer to received the + * converted data. + * @param a_uOutputDataSize Size of the output buffer in SI_CHAR. + * @return true if all of the input data was successfully + * converted. + */ + bool ConvertFromStore( + const char * a_pInputData, + size_t a_uInputDataLen, + SI_CHAR * a_pOutputData, + size_t a_uOutputDataSize) + { + // ASCII/MBCS/UTF-8 needs no conversion + if (a_uInputDataLen > a_uOutputDataSize) { + return false; + } + memcpy(a_pOutputData, a_pInputData, a_uInputDataLen); + return true; + } + + /** Calculate the number of char required by the storage format of this + * data. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated string to calculate the number of + * bytes required to be converted to storage format. + * @return Number of bytes required by the string when + * converted to storage format. This size always + * includes space for the terminating NULL character. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeToStore( + const SI_CHAR * a_pInputData) + { + // ASCII/MBCS/UTF-8 needs no conversion + return strlen((const char *)a_pInputData) + 1; + } + + /** Convert the input string to the storage format of this data. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated source string to convert. All of + * the data will be converted including the + * terminating NULL character. + * @param a_pOutputData Pointer to the buffer to receive the converted + * string. + * @param a_uOutputDataSize Size of the output buffer in char. + * @return true if all of the input data, including the + * terminating NULL character was successfully + * converted. + */ + bool ConvertToStore( + const SI_CHAR * a_pInputData, + char * a_pOutputData, + size_t a_uOutputDataSize) + { + // calc input string length (SI_CHAR type and size independent) + size_t uInputLen = strlen((const char *)a_pInputData) + 1; + if (uInputLen > a_uOutputDataSize) { + return false; + } + + // ascii/UTF-8 needs no conversion + memcpy(a_pOutputData, a_pInputData, uInputLen); + return true; + } +}; + + +// --------------------------------------------------------------------------- +// SI_CONVERT_GENERIC +// --------------------------------------------------------------------------- +#ifdef SI_CONVERT_GENERIC + +#define SI_Case SI_GenericCase +#define SI_NoCase SI_GenericNoCase + +#include +#include "ConvertUTF.h" + +/** + * Converts UTF-8 to a wchar_t (or equivalent) using the Unicode reference + * library functions. This can be used on all platforms. + */ +template +class SI_ConvertW { + bool m_bStoreIsUtf8; +protected: + SI_ConvertW() { } +public: + SI_ConvertW(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) { } + + /* copy and assignment */ + SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } + SI_ConvertW & operator=(const SI_ConvertW & rhs) { + m_bStoreIsUtf8 = rhs.m_bStoreIsUtf8; + return *this; + } + + /** Calculate the number of SI_CHAR required for converting the input + * from the storage format. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to SI_CHAR. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @return Number of SI_CHAR required by the string when + * converted. If there are embedded NULL bytes in the + * input data, only the string up and not including + * the NULL byte will be converted. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeFromStore( + const char * a_pInputData, + size_t a_uInputDataLen) + { + SI_ASSERT(a_uInputDataLen != (size_t) -1); + + if (m_bStoreIsUtf8) { + // worst case scenario for UTF-8 to wchar_t is 1 char -> 1 wchar_t + // so we just return the same number of characters required as for + // the source text. + return a_uInputDataLen; + } + else { + return mbstowcs(NULL, a_pInputData, a_uInputDataLen); + } + } + + /** Convert the input string from the storage format to SI_CHAR. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to SI_CHAR. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @param a_pOutputData Pointer to the output buffer to received the + * converted data. + * @param a_uOutputDataSize Size of the output buffer in SI_CHAR. + * @return true if all of the input data was successfully + * converted. + */ + bool ConvertFromStore( + const char * a_pInputData, + size_t a_uInputDataLen, + SI_CHAR * a_pOutputData, + size_t a_uOutputDataSize) + { + if (m_bStoreIsUtf8) { + // This uses the Unicode reference implementation to do the + // conversion from UTF-8 to wchar_t. The required files are + // ConvertUTF.h and ConvertUTF.c which should be included in + // the distribution but are publically available from unicode.org + // at http://www.unicode.org/Public/PROGRAMS/CVTUTF/ + ConversionResult retval; + const UTF8 * pUtf8 = (const UTF8 *) a_pInputData; + if (sizeof(wchar_t) == sizeof(UTF32)) { + UTF32 * pUtf32 = (UTF32 *) a_pOutputData; + retval = ConvertUTF8toUTF32( + &pUtf8, pUtf8 + a_uInputDataLen, + &pUtf32, pUtf32 + a_uOutputDataSize, + lenientConversion); + } + else if (sizeof(wchar_t) == sizeof(UTF16)) { + UTF16 * pUtf16 = (UTF16 *) a_pOutputData; + retval = ConvertUTF8toUTF16( + &pUtf8, pUtf8 + a_uInputDataLen, + &pUtf16, pUtf16 + a_uOutputDataSize, + lenientConversion); + } + return retval == conversionOK; + } + else { + size_t retval = mbstowcs(a_pOutputData, + a_pInputData, a_uOutputDataSize); + return retval != (size_t)(-1); + } + } + + /** Calculate the number of char required by the storage format of this + * data. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated string to calculate the number of + * bytes required to be converted to storage format. + * @return Number of bytes required by the string when + * converted to storage format. This size always + * includes space for the terminating NULL character. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeToStore( + const SI_CHAR * a_pInputData) + { + if (m_bStoreIsUtf8) { + // worst case scenario for wchar_t to UTF-8 is 1 wchar_t -> 6 char + size_t uLen = 0; + while (a_pInputData[uLen]) { + ++uLen; + } + return (6 * uLen) + 1; + } + else { + size_t uLen = wcstombs(NULL, a_pInputData, 0); + if (uLen == (size_t)(-1)) { + return uLen; + } + return uLen + 1; // include NULL terminator + } + } + + /** Convert the input string to the storage format of this data. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated source string to convert. All of + * the data will be converted including the + * terminating NULL character. + * @param a_pOutputData Pointer to the buffer to receive the converted + * string. + * @param a_uOutputDataSize Size of the output buffer in char. + * @return true if all of the input data, including the + * terminating NULL character was successfully + * converted. + */ + bool ConvertToStore( + const SI_CHAR * a_pInputData, + char * a_pOutputData, + size_t a_uOutputDataSize + ) + { + if (m_bStoreIsUtf8) { + // calc input string length (SI_CHAR type and size independent) + size_t uInputLen = 0; + while (a_pInputData[uInputLen]) { + ++uInputLen; + } + ++uInputLen; // include the NULL char + + // This uses the Unicode reference implementation to do the + // conversion from wchar_t to UTF-8. The required files are + // ConvertUTF.h and ConvertUTF.c which should be included in + // the distribution but are publically available from unicode.org + // at http://www.unicode.org/Public/PROGRAMS/CVTUTF/ + ConversionResult retval; + UTF8 * pUtf8 = (UTF8 *) a_pOutputData; + if (sizeof(wchar_t) == sizeof(UTF32)) { + const UTF32 * pUtf32 = (const UTF32 *) a_pInputData; + retval = ConvertUTF32toUTF8( + &pUtf32, pUtf32 + uInputLen, + &pUtf8, pUtf8 + a_uOutputDataSize, + lenientConversion); + } + else if (sizeof(wchar_t) == sizeof(UTF16)) { + const UTF16 * pUtf16 = (const UTF16 *) a_pInputData; + retval = ConvertUTF16toUTF8( + &pUtf16, pUtf16 + uInputLen, + &pUtf8, pUtf8 + a_uOutputDataSize, + lenientConversion); + } + return retval == conversionOK; + } + else { + size_t retval = wcstombs(a_pOutputData, + a_pInputData, a_uOutputDataSize); + return retval != (size_t) -1; + } + } +}; + +#endif // SI_CONVERT_GENERIC + + +// --------------------------------------------------------------------------- +// SI_CONVERT_ICU +// --------------------------------------------------------------------------- +#ifdef SI_CONVERT_ICU + +#define SI_Case SI_GenericCase +#define SI_NoCase SI_GenericNoCase + +#include + +/** + * Converts MBCS/UTF-8 to UChar using ICU. This can be used on all platforms. + */ +template +class SI_ConvertW { + const char * m_pEncoding; + UConverter * m_pConverter; +protected: + SI_ConvertW() : m_pEncoding(NULL), m_pConverter(NULL) { } +public: + SI_ConvertW(bool a_bStoreIsUtf8) : m_pConverter(NULL) { + m_pEncoding = a_bStoreIsUtf8 ? "UTF-8" : NULL; + } + + /* copy and assignment */ + SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } + SI_ConvertW & operator=(const SI_ConvertW & rhs) { + m_pEncoding = rhs.m_pEncoding; + m_pConverter = NULL; + return *this; + } + ~SI_ConvertW() { if (m_pConverter) ucnv_close(m_pConverter); } + + /** Calculate the number of UChar required for converting the input + * from the storage format. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to UChar. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @return Number of UChar required by the string when + * converted. If there are embedded NULL bytes in the + * input data, only the string up and not including + * the NULL byte will be converted. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeFromStore( + const char * a_pInputData, + size_t a_uInputDataLen) + { + SI_ASSERT(a_uInputDataLen != (size_t) -1); + + UErrorCode nError; + + if (!m_pConverter) { + nError = U_ZERO_ERROR; + m_pConverter = ucnv_open(m_pEncoding, &nError); + if (U_FAILURE(nError)) { + return (size_t) -1; + } + } + + nError = U_ZERO_ERROR; + ucnv_resetToUnicode(m_pConverter); + int32_t nLen = ucnv_toUChars(m_pConverter, NULL, 0, + a_pInputData, (int32_t) a_uInputDataLen, &nError); + if (nError != U_BUFFER_OVERFLOW_ERROR) { + return (size_t) -1; + } + + return (size_t) nLen; + } + + /** Convert the input string from the storage format to UChar. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to UChar. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @param a_pOutputData Pointer to the output buffer to received the + * converted data. + * @param a_uOutputDataSize Size of the output buffer in UChar. + * @return true if all of the input data was successfully + * converted. + */ + bool ConvertFromStore( + const char * a_pInputData, + size_t a_uInputDataLen, + UChar * a_pOutputData, + size_t a_uOutputDataSize) + { + UErrorCode nError; + + if (!m_pConverter) { + nError = U_ZERO_ERROR; + m_pConverter = ucnv_open(m_pEncoding, &nError); + if (U_FAILURE(nError)) { + return false; + } + } + + nError = U_ZERO_ERROR; + ucnv_resetToUnicode(m_pConverter); + ucnv_toUChars(m_pConverter, + a_pOutputData, (int32_t) a_uOutputDataSize, + a_pInputData, (int32_t) a_uInputDataLen, &nError); + if (U_FAILURE(nError)) { + return false; + } + + return true; + } + + /** Calculate the number of char required by the storage format of this + * data. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated string to calculate the number of + * bytes required to be converted to storage format. + * @return Number of bytes required by the string when + * converted to storage format. This size always + * includes space for the terminating NULL character. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeToStore( + const UChar * a_pInputData) + { + UErrorCode nError; + + if (!m_pConverter) { + nError = U_ZERO_ERROR; + m_pConverter = ucnv_open(m_pEncoding, &nError); + if (U_FAILURE(nError)) { + return (size_t) -1; + } + } + + nError = U_ZERO_ERROR; + ucnv_resetFromUnicode(m_pConverter); + int32_t nLen = ucnv_fromUChars(m_pConverter, NULL, 0, + a_pInputData, -1, &nError); + if (nError != U_BUFFER_OVERFLOW_ERROR) { + return (size_t) -1; + } + + return (size_t) nLen + 1; + } + + /** Convert the input string to the storage format of this data. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated source string to convert. All of + * the data will be converted including the + * terminating NULL character. + * @param a_pOutputData Pointer to the buffer to receive the converted + * string. + * @param a_pOutputDataSize Size of the output buffer in char. + * @return true if all of the input data, including the + * terminating NULL character was successfully + * converted. + */ + bool ConvertToStore( + const UChar * a_pInputData, + char * a_pOutputData, + size_t a_uOutputDataSize) + { + UErrorCode nError; + + if (!m_pConverter) { + nError = U_ZERO_ERROR; + m_pConverter = ucnv_open(m_pEncoding, &nError); + if (U_FAILURE(nError)) { + return false; + } + } + + nError = U_ZERO_ERROR; + ucnv_resetFromUnicode(m_pConverter); + ucnv_fromUChars(m_pConverter, + a_pOutputData, (int32_t) a_uOutputDataSize, + a_pInputData, -1, &nError); + if (U_FAILURE(nError)) { + return false; + } + + return true; + } +}; + +#endif // SI_CONVERT_ICU + + +// --------------------------------------------------------------------------- +// SI_CONVERT_WIN32 +// --------------------------------------------------------------------------- +#ifdef SI_CONVERT_WIN32 + +#define SI_Case SI_GenericCase + +// Windows CE doesn't have errno or MBCS libraries +#ifdef _WIN32_WCE +# ifndef SI_NO_MBCS +# define SI_NO_MBCS +# endif +#endif + +#include +#ifdef SI_NO_MBCS +# define SI_NoCase SI_GenericNoCase +#else // !SI_NO_MBCS +/** + * Case-insensitive comparison class using Win32 MBCS functions. This class + * returns a case-insensitive semi-collation order for MBCS text. It may not + * be safe for UTF-8 text returned in char format as we don't know what + * characters will be folded by the function! Therefore, if you are using + * SI_CHAR == char and SetUnicode(true), then you need to use the generic + * SI_NoCase class instead. + */ +#include +template +struct SI_NoCase { + bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { + if (sizeof(SI_CHAR) == sizeof(char)) { + return _mbsicmp((const unsigned char *)pLeft, + (const unsigned char *)pRight) < 0; + } + if (sizeof(SI_CHAR) == sizeof(wchar_t)) { + return _wcsicmp((const wchar_t *)pLeft, + (const wchar_t *)pRight) < 0; + } + return SI_GenericNoCase()(pLeft, pRight); + } +}; +#endif // SI_NO_MBCS + +/** + * Converts MBCS and UTF-8 to a wchar_t (or equivalent) on Windows. This uses + * only the Win32 functions and doesn't require the external Unicode UTF-8 + * conversion library. It will not work on Windows 95 without using Microsoft + * Layer for Unicode in your application. + */ +template +class SI_ConvertW { + UINT m_uCodePage; +protected: + SI_ConvertW() { } +public: + SI_ConvertW(bool a_bStoreIsUtf8) { + m_uCodePage = a_bStoreIsUtf8 ? CP_UTF8 : CP_ACP; + } + + /* copy and assignment */ + SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } + SI_ConvertW & operator=(const SI_ConvertW & rhs) { + m_uCodePage = rhs.m_uCodePage; + return *this; + } + + /** Calculate the number of SI_CHAR required for converting the input + * from the storage format. The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to SI_CHAR. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @return Number of SI_CHAR required by the string when + * converted. If there are embedded NULL bytes in the + * input data, only the string up and not including + * the NULL byte will be converted. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeFromStore( + const char * a_pInputData, + size_t a_uInputDataLen) + { + SI_ASSERT(a_uInputDataLen != (size_t) -1); + + int retval = MultiByteToWideChar( + m_uCodePage, 0, + a_pInputData, (int) a_uInputDataLen, + 0, 0); + return (size_t)(retval > 0 ? retval : -1); + } + + /** Convert the input string from the storage format to SI_CHAR. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData Data in storage format to be converted to SI_CHAR. + * @param a_uInputDataLen Length of storage format data in bytes. This + * must be the actual length of the data, including + * NULL byte if NULL terminated string is required. + * @param a_pOutputData Pointer to the output buffer to received the + * converted data. + * @param a_uOutputDataSize Size of the output buffer in SI_CHAR. + * @return true if all of the input data was successfully + * converted. + */ + bool ConvertFromStore( + const char * a_pInputData, + size_t a_uInputDataLen, + SI_CHAR * a_pOutputData, + size_t a_uOutputDataSize) + { + int nSize = MultiByteToWideChar( + m_uCodePage, 0, + a_pInputData, (int) a_uInputDataLen, + (wchar_t *) a_pOutputData, (int) a_uOutputDataSize); + return (nSize > 0); + } + + /** Calculate the number of char required by the storage format of this + * data. The storage format is always UTF-8. + * + * @param a_pInputData NULL terminated string to calculate the number of + * bytes required to be converted to storage format. + * @return Number of bytes required by the string when + * converted to storage format. This size always + * includes space for the terminating NULL character. + * @return -1 cast to size_t on a conversion error. + */ + size_t SizeToStore( + const SI_CHAR * a_pInputData) + { + int retval = WideCharToMultiByte( + m_uCodePage, 0, + (const wchar_t *) a_pInputData, -1, + 0, 0, 0, 0); + return (size_t) (retval > 0 ? retval : -1); + } + + /** Convert the input string to the storage format of this data. + * The storage format is always UTF-8 or MBCS. + * + * @param a_pInputData NULL terminated source string to convert. All of + * the data will be converted including the + * terminating NULL character. + * @param a_pOutputData Pointer to the buffer to receive the converted + * string. + * @param a_pOutputDataSize Size of the output buffer in char. + * @return true if all of the input data, including the + * terminating NULL character was successfully + * converted. + */ + bool ConvertToStore( + const SI_CHAR * a_pInputData, + char * a_pOutputData, + size_t a_uOutputDataSize) + { + int retval = WideCharToMultiByte( + m_uCodePage, 0, + (const wchar_t *) a_pInputData, -1, + a_pOutputData, (int) a_uOutputDataSize, 0, 0); + return retval > 0; + } +}; + +#endif // SI_CONVERT_WIN32 + + +// --------------------------------------------------------------------------- +// TYPE DEFINITIONS +// --------------------------------------------------------------------------- + +typedef CSimpleIniTempl,SI_ConvertA > CSimpleIniA; +typedef CSimpleIniTempl,SI_ConvertA > CSimpleIniCaseA; + +#if defined(SI_CONVERT_ICU) +typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniW; +typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniCaseW; +#else +typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniW; +typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniCaseW; +#endif + +#ifdef _UNICODE +# define CSimpleIni CSimpleIniW +# define CSimpleIniCase CSimpleIniCaseW +# define SI_NEWLINE SI_NEWLINE_W +#else // !_UNICODE +# define CSimpleIni CSimpleIniA +# define CSimpleIniCase CSimpleIniCaseA +# define SI_NEWLINE SI_NEWLINE_A +#endif // _UNICODE + +#ifdef _MSC_VER +# pragma warning (pop) +#endif + +#endif // INCLUDED_SimpleIni_h + diff --git a/corelib/src/VWDictionary.cpp b/corelib/src/VWDictionary.cpp new file mode 100644 index 0000000000..001f16e207 --- /dev/null +++ b/corelib/src/VWDictionary.cpp @@ -0,0 +1,1136 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "VWDictionary.h" + +#include "VisualWord.h" +#include "Signature.h" +#include "rtabmap/core/DBDriver.h" +#include "NearestNeighbor.h" +#include "rtabmap/core/Parameters.h" + +#include "utilite/UtiLite.h" + +#include +#include + +namespace rtabmap +{ + +const int VWDictionary::ID_START = 1; +const int VWDictionary::ID_INVALID = 0; + +VWDictionary::VWDictionary(const ParametersMap & parameters) : + _lastNewWordsAddedCount(0), + _totalActiveReferences(0), + _incrementalDictionary(Parameters::defaultKpIncrementalDictionary()), + _minDistUsed(Parameters::defaultKpMinDistUsed()), + _minDist(Parameters::defaultKpMinDist()), + _nndrUsed(Parameters::defaultKpNndrUsed()), + _nndrRatio(Parameters::defaultKpNndrRatio()), + _maxLeafs(Parameters::defaultKpMaxLeafs()), + _dictionaryPath(Parameters::defaultKpDictionaryPath()), + _dim(0), + _lastWordId(0), + _nn(0) +{ + this->parseParameters(parameters); +} + +VWDictionary::~VWDictionary() +{ + this->clear(); + if(_nn) + { + delete _nn; + } +} + +void VWDictionary::parseParameters(const ParametersMap & parameters) +{ + ParametersMap::const_iterator iter; + if((iter=parameters.find(Parameters::kKpMinDistUsed())) != parameters.end()) + { + _minDistUsed = uStr2Bool((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kKpMinDist())) != parameters.end()) + { + this->setMinDist(std::atof((*iter).second.c_str())); + } + if((iter=parameters.find(Parameters::kKpNndrUsed())) != parameters.end()) + { + _nndrUsed = uStr2Bool((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kKpNndrRatio())) != parameters.end()) + { + this->setNndrRatio(std::atof((*iter).second.c_str())); + } + if((iter=parameters.find(Parameters::kKpMaxLeafs())) != parameters.end()) + { + _maxLeafs = (unsigned int)std::atoi((*iter).second.c_str()); + } + + std::string dictionaryPath = _dictionaryPath; + bool incrementalDictionary = _incrementalDictionary; + if((iter=parameters.find(Parameters::kKpDictionaryPath())) != parameters.end()) + { + dictionaryPath = (*iter).second.c_str(); + } + if((iter=parameters.find(Parameters::kKpIncrementalDictionary())) != parameters.end()) + { + incrementalDictionary = uStr2Bool((*iter).second.c_str()); + } + + NNStrategy nnStrategy = kNNUndef; + // Verifying hypotheses strategy + if((iter=parameters.find(Parameters::kKpNNStrategy())) != parameters.end()) + { + nnStrategy = (NNStrategy)std::atoi((*iter).second.c_str()); + } + NNStrategy currentNNStrategy = this->nnStrategy(); + if(!_nn || ( nnStrategy!=kNNUndef && (nnStrategy != currentNNStrategy) ) ) + { + this->setNNStrategy(nnStrategy, parameters); + } + else if(_nn) + { + _nn->parseParameters(parameters); + } + + this->setIncrementalDictionary(incrementalDictionary, dictionaryPath); +} + +void VWDictionary::setIncrementalDictionary(bool incrementalDictionary, const std::string & dictionaryPath) +{ + if(!incrementalDictionary) + { + if((!_incrementalDictionary && _dictionaryPath.compare(dictionaryPath) != 0) || + _visualWords.size() == 0) + { + std::ifstream file; + if(!dictionaryPath.empty()) + { + file.open(dictionaryPath.c_str(), std::ifstream::in); + } + if(file.good()) + { + UDEBUG("Deleting old dictionary and loading the new one from \"%s\"", dictionaryPath.c_str()); + UTimer timer; + + // first line is the header + std::string str; + std::list strList; + std::getline(file, str); + strList = uSplitNumChar(str); + unsigned int dimension = 0; + for(std::list::iterator iter = strList.begin(); iter != strList.end(); ++iter) + { + if(uIsDigit(iter->at(0))) + { + dimension = std::atoi(iter->c_str()); + break; + } + } + + if(dimension == 0 || dimension > 1000) + { + UERROR("Invalid dictionary file, visual word dimension (%d) is not valid, \"%s\"", dimension, dictionaryPath.c_str()); + } + else + { + // Process all words + while(file.good()) + { + std::getline(file, str); + strList = uSplit(str); + if(strList.size() == dimension+1) + { + //first one is the visual word id + std::list::iterator iter = strList.begin(); + int id = std::atoi(iter->c_str()); + std::vector descriptor(dimension); + ++iter; + unsigned int i=0; + + //get descriptor + for(;ic_str()); + } + if(i != dimension) + { + UERROR(""); + } + + // laplacian not used + VisualWord * vw = new VisualWord(id, &(descriptor[0]), dimension, 0); + _visualWords.insert(_visualWords.end(), std::pair(id, vw)); + } + else + { + UWARN("Cannot parse line \"%s\"", str.c_str()); + } + } + this->update(); + _incrementalDictionary = false; + } + + + UDEBUG("Time changing dictionary = %fs", timer.ticks()); + } + else + { + UERROR("Cannot open dictionary file \"%s\"", dictionaryPath.c_str()); + } + file.close(); + } + else if(!_incrementalDictionary) + { + UDEBUG("Dictionary \"%s\" already loaded...", dictionaryPath.c_str()); + } + else + { + UERROR("Cannot change to a fixed dictionary if there are already words (%d) in the incremental one.", _visualWords.size()); + } + } + else if(incrementalDictionary && !_incrementalDictionary) + { + _incrementalDictionary = true; + if(_visualWords.size()) + { + UWARN("Incremental dictionary set: already loaded visual words (%d) from the fixed dictionary will be included in the incremental one.", _visualWords.size()); + } + } + _dictionaryPath = dictionaryPath; +} + +void VWDictionary::setNNStrategy(NNStrategy strategy, const ParametersMap & parameters) +{ + if(strategy!=kNNUndef) + { + if(_nn) + { + delete _nn; + _nn = 0; + } + switch(strategy) + { + case kNNKdTree: + _nn = new KdTreeNN(parameters); + break; + case kNNFlannKdTree: + _nn = new FlannKdTreeNN(parameters); + break; + case kNNNaive: + default: + // do nothing... _nn must stay = 0 + break; + } + if(_nn && !_dataTree.empty()) + { + _nn->setData(_dataTree); + } + else if(!_nn) + { + _dataTree = cv::Mat(); + } + this->update(); + } +} + +VWDictionary::NNStrategy VWDictionary::nnStrategy() const +{ + NNStrategy strategy = kNNUndef; + KdTreeNN * kdTree = dynamic_cast(_nn); + FlannKdTreeNN * flannKdTree = dynamic_cast(_nn); + if(kdTree) + { + strategy = kNNKdTree; + } + else if(flannKdTree) + { + strategy = kNNFlannKdTree; + } + else + { + strategy = kNNNaive; + } + return strategy; +} + +void VWDictionary::setMinDist(float d) +{ + if(d < 0) + { + ULOGGER_ERROR("Match threshold must be positive (%f)", d); + } + else + { + _minDist = d; + } + +} +void VWDictionary::setNndrRatio(float ratio) +{ + if(ratio < 0) + { + ULOGGER_ERROR("Ratio must be positive (%f)", ratio); + } + else + { + _nndrRatio = ratio; + } +} + +int VWDictionary::getLastIndexedWordId() const +{ + if(_mapIndexId.size()) + { + return _mapIndexId.rbegin()->second; + } + else + { + return 0; + } +} + +void VWDictionary::update() +{ + ULOGGER_DEBUG(""); + if(!_incrementalDictionary && !_dataTree.empty()) + { + // No need to update the search index if we + // use a fixed dictionary and the index is + // already built + return; + } + + _mapIndexId.clear(); + + if(_nn && _visualWords.size()) + { + UTimer timer; + timer.start(); + + if(!_dim) + { + _dim = _visualWords.begin()->second->getDim(); + } + + // Create the kd-Tree + _dataTree = cv::Mat::zeros(_visualWords.size(), _dim, CV_32F); // SURF descriptors are CV_32F + std::map::const_iterator iter = _visualWords.begin(); + for(unsigned int i=0; i < _visualWords.size(); ++i, ++iter) + { + float * rowFl = _dataTree.ptr(i); + if(iter->second->getDim() == _dim) + { + memcpy(rowFl, iter->second->getDescriptor(), _dim*sizeof(float)); + _mapIndexId.insert(_mapIndexId.end(), std::pair(i, iter->second->id())); + } + else + { + ULOGGER_WARN("A word is not the same size than the dictionary, ignoring that word..."); + _mapIndexId.insert(_mapIndexId.end(), std::pair(i, 0)); // set to INVALID + } + } + + ULOGGER_DEBUG("_mapIndexId.size() = %d, words.size()=%d",_mapIndexId.size(), _visualWords.size()); + ULOGGER_DEBUG("copying data = %f s", timer.ticks()); + + // Update the nearest neighbor algorithm + _nn->setData(_dataTree); + + ULOGGER_DEBUG("Time to create kd tree = %f s", timer.ticks()); + } + _lastNewWordsAddedCount = 0; +} + +void VWDictionary::clear() +{ + ULOGGER_DEBUG("%d words destroyed", _visualWords.size()); + for(std::map::iterator i=_visualWords.begin(); i!=_visualWords.end(); ++i) + { + delete (*i).second; + } + _visualWords.clear(); + _lastNewWordsAddedCount = 0; + _totalActiveReferences = 0; + _lastWordId = 0; +} + +int VWDictionary::getNextId() +{ + return ++_lastWordId; +} + +void VWDictionary::addWordRef(int wordId, int signatureId) +{ + VisualWord * vw = 0; + vw = uValue(_visualWords, wordId, vw); + if(vw) + { + vw->addRef(signatureId); + _totalActiveReferences += 1; + _unusedWords.erase(vw->id()); + } +} + +void VWDictionary::removeAllWordRef(int wordId, int signatureId) +{ + VisualWord * vw = 0; + vw = uValue(_visualWords, wordId, vw); + if(vw) + { + _totalActiveReferences -= vw->removeAllRef(signatureId); + if(vw->getReferences().size() == 0) + { + _unusedWords.insert(std::pair(vw->id(), vw)); + } + } +} + +std::list VWDictionary::addNewWords(const std::list > & descriptors, + unsigned int dim, + int signatureId) +{ + UTimer timer; + std::list wordIds; + ULOGGER_DEBUG(""); + if(_dim && _dim != dim && dim) + { + ULOGGER_WARN("Descriptor size has changed! (%d to %d), Nearest neighbor approaches may not work with different descriptor sizes.", _dim, dim); + } + else if(!dim) + { + ULOGGER_ERROR("Descriptor size is null?!?"); + return wordIds; + } + _dim = dim; + if (descriptors.size() == 0 || !_dim) + { + ULOGGER_ERROR("Parameters don't fit the requirements of this method"); + return wordIds; + } + + int newWordsCount= 0; + int dupWordsCount= 0; + + unsigned int k=1; // k nearest neighbors + if(_nndrUsed) + { + k = 2; + } + + if(_nn) + { + std::list newWords; + + cv::Mat results(descriptors.size(), k, CV_32SC1); // results index + cv::Mat dists; + if(_nn->isDist64F()) + { + dists = cv::Mat(descriptors.size(), k, CV_64FC1); // Distance results are CV_64FC1; + } + else + { + dists = cv::Mat(descriptors.size(), k, CV_32FC1); // Distance results are CV_32FC1 + } + cv::Mat newPts(descriptors.size(), _dim, CV_32F); // SURF descriptors are CV_32F + + // fill the request matrix + std::list >::const_iterator itDesc = descriptors.begin(); + for(unsigned int i=0; i(i); + if(itDesc->size() == _dim) + { + memcpy(rowFl, (const float *)itDesc->data(), _dim*sizeof(float)); + } + else + { + ULOGGER_WARN("Descriptors are not the same size! The result may be wrong..."); + } + } + + UTimer timerLocal; + timerLocal.start(); + + if(!_dataTree.empty()) + { + //Find nearest neighbors + _nn->search(newPts, results, dists, k, _maxLeafs); + ULOGGER_DEBUG("Time to find nn = %f s", timerLocal.ticks()); + } + + // + for(unsigned int i = 0; i < descriptors.size(); ++i) + { + // Check if this descriptor matches with a word from the last signature (a word not already added to the tree) + std::map fullResults; // Contains results from the kd-tree search and the naive search in new words + this->naiveNNSearch(newWords, newPts.ptr(i), _dim, fullResults, k); + + if(!_dataTree.empty()) + { + for(unsigned int j=0; jisDist64F()) + { + dist = (float)dists.at(i,j); + } + else + { + dist = dists.at(i,j); + } + if(!_nn->isDistSquared()) + { + dist*=dist; + } + + fullResults.insert(std::pair(dist, uValue(_mapIndexId, results.at(i,j)))); + } + } + + if(_incrementalDictionary) + { + bool badDist = false; + if(fullResults.size() == 0) + { + badDist = true; + } + if(!badDist && _minDistUsed && fullResults.begin()->first > _minDist) + { + badDist = true; + } + if(!badDist && _nndrUsed) + { + if(fullResults.size() >= 2) + { + // Apply NNDR + if(fullResults.begin()->first > _nndrRatio * (++fullResults.begin())->first) + { + badDist = true; // Rejected + } + } + else + { + UWARN("Not enough nearest neighbors found!"); + badDist = true; // Rejected + } + } + + if(badDist) + { + VisualWord * vw = new VisualWord(getNextId(), newPts.ptr(i), _dim, signatureId); + _visualWords.insert(_visualWords.end(), std::pair(vw->id(), vw)); + newWords.push_back(vw); + wordIds.push_back(vw->id()); + ++newWordsCount; + } + else + { + ++dupWordsCount; + this->addWordRef(fullResults.begin()->second, signatureId); + wordIds.push_back(fullResults.begin()->second); + } + } + else if(fullResults.size()) + { + // If the dictionary is not incremental, just take the nearest word + ++dupWordsCount; + this->addWordRef(fullResults.begin()->second, signatureId); + wordIds.push_back(fullResults.begin()->second); + } + } + ULOGGER_DEBUG("naive search and add ref/words time = %f s", timerLocal.ticks()); + } + else //Naive nearest neighbor + { + ULOGGER_DEBUG("Naive NN"); + UTimer timer; + timer.start(); + std::list >::const_iterator itDesc = descriptors.begin(); + for(; itDesc!=descriptors.end();++itDesc) + { + const float* d = itDesc->data(); + + std::map results; + naiveNNSearch(uValuesList(_visualWords), d, _dim, results, k); + + if(_incrementalDictionary) + { + bool badDist = false; + if(results.size() == 0) + { + badDist = true; + } + if(!badDist && _minDistUsed && results.begin()->first > _minDist) + { + badDist = true; + } + if(!badDist && _nndrUsed) + { + if(results.size() >= 2) + { + // Apply NNDR + if(results.begin()->first > _nndrRatio * (++results.begin())->first) + { + badDist = true; // Rejected + } + } + else + { + badDist = true; // Rejected + } + } + + if(badDist) + { + ++newWordsCount; + VisualWord * vw = new VisualWord(getNextId(), d, _dim, signatureId); + _visualWords.insert(_visualWords.end(), std::pair(vw->id(), vw)); + wordIds.push_back(vw->id()); + } + else + { + ++dupWordsCount; + this->addWordRef(results.begin()->second, signatureId); + wordIds.push_back(results.begin()->second); + } + } + else if(results.size()) + { + // If the dictionary is not incremental, just take the nearest word + ++dupWordsCount; + this->addWordRef(results.begin()->second, signatureId); + wordIds.push_back(results.begin()->second); + } + } + ULOGGER_DEBUG("Naive search time = %fs", timer.ticks()); + } + + ULOGGER_DEBUG("%d new words added...", newWordsCount); + ULOGGER_DEBUG("%d duplicated words added...", dupWordsCount); + UDEBUG("total time %fs", timer.ticks()); + + _lastNewWordsAddedCount = newWordsCount; + _totalActiveReferences += newWordsCount; + return wordIds; +} + +std::vector VWDictionary::findNN(const std::list & vws, bool searchInNewlyAddedWords) const +{ + UTimer timer; + timer.start(); + std::vector resultIds(vws.size(), 0); + unsigned int k=1; // k nearest neighbor + if(_nndrUsed) + { + k=2; + } + if(_nn && _dim) + { + if(_visualWords.size() && vws.size() && vws.front()) + { + cv::Mat results(vws.size(), k, CV_32SC1); // results index + cv::Mat dists; + cv::Mat resultsNotIndexed(vws.size(), k, CV_32SC1); + cv::Mat distsNotIndexed; + if(_nn->isDist64F()) + { + dists = cv::Mat(vws.size(), k, CV_64FC1); // Distance results are CV_64FC1; + distsNotIndexed = cv::Mat(vws.size(), k, CV_64FC1); // Distance results are CV_64FC1; + } + else + { + dists = cv::Mat(vws.size(), k, CV_32FC1); // Distance results are CV_32FC1 + distsNotIndexed = cv::Mat(vws.size(), k, CV_32FC1); // Distance results are CV_32FC1 + } + cv::Mat newPts(vws.size(), _dim, CV_32F); // SURF descriptors are CV_32F + + // fill the request matrix + int index = 0; + VisualWord * vw; + for(std::list::const_iterator iter=vws.begin(); iter!=vws.end(); ++iter, ++index) + { + vw = *iter; + float * rowFl = newPts.ptr(index); + if(vw->getDim() == _dim) + { + memcpy(rowFl, vw->getDescriptor(), _dim*sizeof(float)); + } + else + { + ULOGGER_WARN("Descriptors are not the same size! The result may be wrong..."); + } + } + ULOGGER_DEBUG("Preparation time = %fs", timer.ticks()); + + if(!_dataTree.empty()) + { + _nn->search(newPts, results, dists, k, _maxLeafs); + } + ULOGGER_DEBUG("Search dictionary time = %fs", timer.ticks()); + + std::map mapIndexIdNotIndexed; + unsigned int unreferencedWordsCount = _visualWords.size() - _mapIndexId.size(); + if(searchInNewlyAddedWords && unreferencedWordsCount) + { + cv::Mat dataNotIndexed = cv::Mat::zeros(unreferencedWordsCount, _dim, CV_32F); + unsigned int index = 0; + VisualWord * vw; + for(std::map::const_reverse_iterator iter = _visualWords.rbegin(); + iter != _visualWords.rend() && index < unreferencedWordsCount; + ++iter, ++index) + { + vw = iter->second; + float * rowFl = dataNotIndexed.ptr(index); + if(vw->getDim() == _dim) + { + memcpy(rowFl, vw->getDescriptor(), _dim*sizeof(float)); + mapIndexIdNotIndexed.insert(mapIndexIdNotIndexed.end(), std::pair(index, vw->id())); + } + else + { + ULOGGER_WARN("Descriptors are not the same size! The result may be wrong..."); + mapIndexIdNotIndexed.insert(mapIndexIdNotIndexed.end(), std::pair(index, 0)); // invalid + } + } + + // Find nearest neighbor + ULOGGER_DEBUG("Searching in words not indexed..."); + _nn->search(dataNotIndexed, newPts, resultsNotIndexed, distsNotIndexed, k, _maxLeafs); + } + ULOGGER_DEBUG("Search not yet indexed words time = %fs", timer.ticks()); + + for(unsigned int i=0; i fullResults; // Contains results from the kd-tree search [and the naive search in new words] + + for(unsigned int j=0; jisDist64F()) + { + dist = (float)dists.at(i,j); + } + else + { + dist = dists.at(i,j); + } + if(!_nn->isDistSquared()) + { + dist*=dist; + } + + fullResults.insert(std::pair(dist, uValue(_mapIndexId, results.at(i,j)))); + } + if(searchInNewlyAddedWords && unreferencedWordsCount) + { + if(_nn->isDist64F()) + { + dist = (float)distsNotIndexed.at(i,j); + } + else + { + dist = distsNotIndexed.at(i,j); + } + if(!_nn->isDistSquared()) + { + dist*=dist; + } + + fullResults.insert(std::pair(dist, uValue(mapIndexIdNotIndexed, resultsNotIndexed.at(i,j)))); + } + } + + if(_incrementalDictionary) + { + bool badDist = false; + if(fullResults.size() == 0) + { + badDist = true; + } + if(!badDist && _minDistUsed && fullResults.begin()->first > _minDist) + { + badDist = true; + } + if(!badDist && _nndrUsed) + { + if(fullResults.size() >= 2) + { + // Apply NNDR + if(fullResults.begin()->first > _nndrRatio * (++fullResults.begin())->first) + { + badDist = true; // Rejected + } + } + else + { + badDist = true; // Rejected + } + } + + if(!badDist) + { + resultIds[i] = fullResults.begin()->second; // Accepted + } + } + else if(fullResults.size()) + { + //Just take the nearest if the dictionary is not incremental + resultIds[i] = fullResults.begin()->second; // Accepted + } + } + ULOGGER_DEBUG("badDist check time = %fs", timer.ticks()); + } + } + else // Naive search + { + ULOGGER_DEBUG("Naive NN"); + UTimer timer; + timer.start(); + int i=0; + std::list values = uValuesList(_visualWords); + for(std::list::const_iterator iter=vws.begin(); iter!=vws.end(); ++iter, ++i) + { + const float* d = (*iter)->getDescriptor(); + unsigned int dim = (*iter)->getDim(); + + std::map results; + naiveNNSearch(values, d, dim, results, k); + + if(_incrementalDictionary) + { + bool badDist = false; + if(results.size() == 0) + { + badDist = true; + } + if(!badDist && _minDistUsed && results.begin()->first > _minDist) + { + badDist = true; + } + if(!badDist && _nndrUsed) + { + if(results.size() >= 2) + { + // Apply NNDR + if(results.begin()->first > _nndrRatio * (++results.begin())->first) + { + badDist = true; // Rejected + } + } + else + { + badDist = true; // Rejected + } + } + + if(!badDist) + { + resultIds[i] = results.begin()->second; // Accepted + } + } + else if(results.size()) + { + // If the dictionary is not incremental, just take the nearest word + resultIds[i] = results.begin()->second; // Accepted + } + } + } + return resultIds; +} + +void VWDictionary::addWord(VisualWord * vw) +{ + if(vw) + { + _visualWords.insert(std::pair(vw->id(), vw)); + _totalActiveReferences += uSum(uValues(vw->getReferences())); + } +} + +// dist = (euclidean dist)^2, "k" nearest neighbors +void VWDictionary::naiveNNSearch(const std::list & words, const float * d, unsigned int length, std::map & results, unsigned int k) const +{ + double total_cost = 0; + double t0, t1, t2, t3; + const float * dw = 0; + bool goodMatch; + + if(!words.size() && k > 0) + { + return; + } + + for(std::list::const_iterator vmi=words.begin(); vmi != words.end(); ++vmi) + { + goodMatch = true; + total_cost = 0; + + if((*vmi)->getDim() == length) + { + dw = (*vmi)->getDescriptor(); + + t0 = 0; + total_cost += t0*t0; + + // compare descriptors + unsigned int i = 0; + if(length>=4) + { + for(; i <= length-4; i += 4 ) + { + t0 = d[i] - dw[i]; + t1 = d[i+1] - dw[i+1]; + t2 = d[i+2] - dw[i+2]; + t3 = d[i+3] - dw[i+3]; + total_cost += t0*t0 + t1*t1 + t2*t2 + t3*t3; + if(results.size() >= k && total_cost > results.rbegin()->first) + { + goodMatch = false; + break; + } + } + } + for(; goodMatch && i < length; ++i) + { + t0 = d[i] - dw[i]; + total_cost += t0*t0; + if(results.size() >= k && total_cost > results.rbegin()->first) + { + goodMatch = false; + break; + } + } + } + else + { + ULOGGER_WARN("Descriptors are not the same length"); + goodMatch = false; + } + if(goodMatch) + { + results.insert(std::pair(total_cost, (*vmi)->id())); + if(results.size() > k) + { + results.erase(--results.end()); + } + } + } +} + +void VWDictionary::getCommonWords(unsigned int nbCommonWords, int totalSign, std::list & commonWords) const +{ + UTimer timer; + timer.start(); + ULOGGER_DEBUG("common words %d, _visualWords.size()=%d)", nbCommonWords, _visualWords.size()); + commonWords.clear(); + if(nbCommonWords && _visualWords.size() && totalSign && nbCommonWords>0) + { + // Sort word by reference count and take the 'nbCommonWords' at the end + std::multimap countMap; // + for(std::map::const_iterator iter=_visualWords.begin(); iter!=_visualWords.end(); ++iter) + { + // Keep at most nbCommonWords words, don't need to keep others + if(countMap.size()>nbCommonWords) + { + if((*iter).second->getTotalReferences() > countMap.begin()->first) + { + countMap.erase(countMap.begin()); + countMap.insert(std::pair((*iter).second->getTotalReferences(), (*iter).first)); + } + } + else + { + countMap.insert(std::pair((*iter).second->getTotalReferences(), (*iter).first)); + } + } + + //ULOGGER_DEBUG("sum = %d , %d, %d", Util::sum(Util::keys(countMap)), Util::sum(Util::keys(countMap)) - 13823, countMap.size()); + + ULOGGER_DEBUG("time = %f s", timer.ticks()); + + bool commonWordsWeighted = true; //TODO, make a variable parameter + if(commonWordsWeighted) + { + // We apply the ratio between the reference counts + // Example : + // add the last id (id=42, totalRef=12) -> {42} + // add the next id (id=78, totalRef=10) -> {42, 78} + // // here totalRef of the next id=23 is 5, i.e. 1/2 ratio of the last one (10), we re-add previous ids + // add the last id (id=42, totalRef=12) -> {42, 78, 42} + // add the next id (id=78, totalRef=10) -> {42, 78, 42, 78} + // add the next id (id=23, totalRef=5) -> {42, 78, 42, 78, 23} + // add the next id (id=169, totalRef=4) -> {42, 78, 42, 78, 23, 169} + // ... to have a total of nbCommonWords reference ids (an id can be more than once in the list) + std::list idsAdded; // + for(std::multimap::reverse_iterator iter=countMap.rbegin(); iter!=countMap.rend() && commonWords.size() < nbCommonWords; ++iter) + { + //ULOGGER_DEBUG("count=%d, wordId=%d", (*iter).first, (*iter).second); + if((*iter).first==0) // No ref? + { + break; + } + + if(iter != countMap.rbegin()) + { + std::multimap::reverse_iterator previous = iter; + --previous; + int ratio = (*previous).first / (*iter).first; + if(ratio>1) + { + for(int r=1; r::iterator j=idsAdded.begin(); j!=idsAdded.end(); ++j) + { + commonWords.push_back(*j); + if(commonWords.size() >= nbCommonWords) + { + break; + } + } + if(commonWords.size() >= nbCommonWords) + { + break; + } + } + } + } + + if(commonWords.size() < nbCommonWords) + { + commonWords.push_back((*iter).second); // + idsAdded.push_back((*iter).second); + } + } + } + else + { + commonWords = uValues(countMap); + } + ULOGGER_DEBUG("time = %f s", timer.ticks()); + } + ULOGGER_DEBUG("size = %d", commonWords.size()); +} + +const VisualWord * VWDictionary::getWord(int id) const +{ + return uValue(_visualWords, id); +} + +void VWDictionary::setWordSaved(int id, bool saved) +{ + VisualWord * w = uValue(_visualWords, id); + if(w) + { + w->setSaved(saved); + } +} + +std::vector VWDictionary::getUnusedWords() const +{ + if(!_incrementalDictionary) + { + ULOGGER_WARN("This method does nothing on a fixed dictionary"); + return std::vector(); + } + return uValues(_unusedWords); +} + +void VWDictionary::removeWords(const std::vector & words) +{ + for(unsigned int i=0; iid()); + _unusedWords.erase(words[i]->id()); + } +} + +void VWDictionary::exportDictionary(const char * fileNameReferences, const char * fileNameDescriptors) const +{ + FILE* foutRef = 0; + FILE* foutDesc = 0; +#ifdef _MSC_VER + fopen_s(&foutRef, fileNameReferences, "w"); + fopen_s(&foutDesc, fileNameDescriptors, "w"); +#else + foutRef = fopen(fileNameReferences, "w"); + foutDesc = fopen(fileNameDescriptors, "w"); +#endif + + if(foutRef) + { + fprintf(foutRef, "WordID SignaturesID...\n"); + } + if(foutDesc) + { + if(_visualWords.begin() == _visualWords.end()) + { + fprintf(foutDesc, "WordID Descriptors...\n"); + } + else + { + fprintf(foutDesc, "WordID Descriptors...%d\n", (*_visualWords.begin()).second->getDim()); + } + } + + for(std::map::const_iterator iter=_visualWords.begin(); iter!=_visualWords.end(); ++iter) + { + // References + if(foutRef) + { + fprintf(foutRef, "%d ", (*iter).first); + const std::map ref = (*iter).second->getReferences(); + for(std::map::const_iterator jter=ref.begin(); jter!=ref.end(); ++jter) + { + for(int i=0; i<(*jter).second; ++i) + { + fprintf(foutRef, "%d ", (*jter).first); + } + } + fprintf(foutRef, "\n"); + } + + //Descriptors + if(foutDesc) + { + fprintf(foutDesc, "%d ", (*iter).first); + const float * desc = (*iter).second->getDescriptor(); + int dim = (*iter).second->getDim(); + + for(int i=0; i. + */ + +#pragma once + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include "VisualWord.h" +#include +#include +#include +#include +#include "rtabmap/core/Parameters.h" + +namespace rtabmap +{ + +class NearestNeighbor; +class DBDriver; + +class RTABMAP_EXP VWDictionary +{ +public: + enum NNStrategy{kNNNaive, kNNKdTree, kNNFlannKdTree, kNNUndef}; + static const int ID_START; + static const int ID_INVALID; + +public: + VWDictionary(const ParametersMap & parameters = ParametersMap()); + virtual ~VWDictionary(); + + virtual void parseParameters(const ParametersMap & parameters); + + virtual void update(); + + virtual std::list addNewWords( + const std::list > & descriptors, + unsigned int dim, + int signatureId); + virtual void addWord(VisualWord * vw); + + virtual std::vector findNN(const std::list & vws, bool searchInNewlyAddedWords = true) const; + void naiveNNSearch(const std::list & words, const float * d, unsigned int length, std::map & results, unsigned int k) const; + + void addWordRef(int wordId, int signatureId); + void removeAllWordRef(int wordId, int signatureId); + const VisualWord * getWord(int id) const; + void setWordSaved(int id, bool saved); + void setLastWordId(int id) {_lastWordId = id;} + void getCommonWords(unsigned int nbCommonWords, int totalSign, std::list & commonWords) const; + const std::map & getVisualWords() const {return _visualWords;} + void setMinDist(float d); + float getMinDist() const {return _minDist;} + bool isMinDistUsed() const {return _minDistUsed;} + void setMinDistUsed(bool used) {_minDistUsed = used;} + void setNndrUsed(bool used) {_nndrUsed = used;} + bool isNndrUsed() const {return _nndrUsed;} + void setNndrRatio(float ratio); + float getNndrRatio() {return _nndrRatio;} + unsigned int getNotIndexedWordsCount() const {return _visualWords.size() - _mapIndexId.size();} + unsigned int getLastNewWordsAddedCount() const {return _lastNewWordsAddedCount;} + int getLastIndexedWordId() const; + int getTotalActiveReferences() const {return _totalActiveReferences;} + void setNNStrategy(NNStrategy strategy, const ParametersMap & parameters = ParametersMap()); + NNStrategy nnStrategy() const; + bool isIncremental() const {return _incrementalDictionary;} + void setIncrementalDictionary(bool incrementalDictionary, const std::string & dictionaryPath); + + void exportDictionary(const char * fileNameReferences, const char * fileNameDescriptors) const; + + void clear(); + std::vector getUnusedWords() const; + unsigned int getUnusedWordsSize() const {return _unusedWords.size();} + void removeWords(const std::vector & words); // caller must delete the words + +protected: + int getNextId(); + +protected: + std::map _visualWords; // + unsigned int _lastNewWordsAddedCount; + int _totalActiveReferences; // keep track of all references for updating the common signature + +private: + bool _incrementalDictionary; + bool _minDistUsed; + float _minDist; //euclidean distance ^ 2 + bool _nndrUsed; + float _nndrRatio; + unsigned int _maxLeafs; + std::string _dictionaryPath; // a pre-computed dictionary (.txt) + unsigned int _dim; + int _lastWordId; + NearestNeighbor * _nn; + cv::Mat _dataTree; + std::map _mapIndexId; + std::map _unusedWords; // +}; + +} // namespace rtabmap diff --git a/corelib/src/VerifyHypotheses.cpp b/corelib/src/VerifyHypotheses.cpp new file mode 100644 index 0000000000..fcb10dc338 --- /dev/null +++ b/corelib/src/VerifyHypotheses.cpp @@ -0,0 +1,595 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "VerifyHypotheses.h" +#include "rtabmap/core/Parameters.h" +#include "Signature.h" +#include "Memory.h" +#include +#include + +#include "utilite/UtiLite.h" + +namespace rtabmap +{ + +VerifyHypotheses::VerifyHypotheses(const ParametersMap & parameters) : + _status(0) +{ + this->parseParameters(parameters); +} + +void VerifyHypotheses::parseParameters(const ParametersMap & parameters) +{ +} + + + +///////////////////////// +// VerifyHypothesesSimple +///////////////////////// +VerifyHypothesesSimple::VerifyHypothesesSimple(const ParametersMap & parameters) : + VerifyHypotheses(parameters) +{ + this->parseParameters(parameters); +} + +VerifyHypothesesSimple::~VerifyHypothesesSimple() +{ +} + +void VerifyHypothesesSimple::parseParameters(const ParametersMap & parameters) +{ + VerifyHypotheses::parseParameters(parameters); +} + +int VerifyHypothesesSimple::verifyHypotheses(const std::list & hypotheses, const Memory * mem) +{ + int hypothesis = 0; + + if(!hypotheses.empty()) + { + for(std::list::const_iterator i = hypotheses.begin(); i!=hypotheses.end(); ++i) + { + if(*i > 0) + { + hypothesis = *i; + break; + } + } + } + + return hypothesis; +} + + +///////////////////////// +// VerifyHypothesesSignSeq +///////////////////////// +/*VerifyHypothesesSignSeq::VerifyHypothesesSignSeq(const ParametersMap & parameters) : + VerifyHypotheses(parameters), + _seqLength(Parameters::defaultVhEpSeqLength()) +{ + this->parseParameters(parameters); +} + +VerifyHypothesesSignSeq::~VerifyHypothesesSignSeq() { + +} + +void VerifyHypothesesSignSeq::parseParameters(const ParametersMap & parameters) +{ + ParametersMap::const_iterator iter; + if((iter=parameters.find(Parameters::kVhEpSeqLength())) != parameters.end()) + { + _seqLength = atoi((*iter).second.c_str()); + } + VerifyHypotheses::parseParameters(parameters); +} + +int VerifyHypothesesSignSeq::verifyHypotheses(const std::list & hypotheses, const Memory * mem) +{ + int hypothesis = 0; + std::map hypothesesToKeep; + + // update hypotheses + std::map::iterator tmp; + for(std::list::const_iterator j=hypotheses.begin(); j!=hypotheses.end(); ++j) + { + // Add it like a new hypothesis + hypothesesToKeep.insert(std::pair(*j, 1)); // NOTE : It will be deleted if an updated hypothesis gives the same id. + + // If we have already this hypothesis, just keep it + tmp = _hypotheses.find(*j); + if(tmp != _hypotheses.end()) + { + hypothesesToKeep.insert(std::pair((*tmp).first, (*tmp).second)); + } + + // Forward hypothesis + tmp = _hypotheses.find(*j-1); + if(tmp != _hypotheses.end()) + { + hypothesesToKeep.insert(std::pair(*j, (*tmp).second+1)); + } + + // Backward hypothesis + tmp = _hypotheses.find(*j+1); + if(tmp != _hypotheses.end()) + { + hypothesesToKeep.insert(std::pair(*j, (*tmp).second-1)); + } + } + + _hypotheses = hypothesesToKeep; + + // if an hypothesis has at least 3 references, return the id + if(_hypotheses.size()>0) + { + for(std::map::iterator i=_hypotheses.begin(); i!=_hypotheses.end(); ++i) + { + if((*i).second > _seqLength) + { + hypothesis = (*i).first; + break; // return the first + } + } + } + + return hypothesis; +}*/ + + + +///////////////////////// +// VerifyHypothesesEpipolarGeo +///////////////////////// +VerifyHypothesesEpipolarGeo::VerifyHypothesesEpipolarGeo(const ParametersMap & parameters) : + VerifyHypotheses(parameters), + _matchCountMinAccepted(Parameters::defaultVhEpMatchCountMin()), + _ransacParam1(Parameters::defaultVhEpRansacParam1()), + _ransacParam2(Parameters::defaultVhEpRansacParam2()) +{ + this->parseParameters(parameters); +} + +VerifyHypothesesEpipolarGeo::~VerifyHypothesesEpipolarGeo() { + +} + +void VerifyHypothesesEpipolarGeo::parseParameters(const ParametersMap & parameters) +{ + ParametersMap::const_iterator iter; + if((iter=parameters.find(Parameters::kVhEpMatchCountMin())) != parameters.end()) + { + _matchCountMinAccepted = std::atoi((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kVhEpRansacParam1())) != parameters.end()) + { + _ransacParam1 = std::atof((*iter).second.c_str()); + } + if((iter=parameters.find(Parameters::kVhEpRansacParam2())) != parameters.end()) + { + _ransacParam2 = std::atof((*iter).second.c_str()); + } + VerifyHypotheses::parseParameters(parameters); +} + +void VerifyHypothesesEpipolarGeo::setStatus(int status) +{ + // Only set if the status was not set before. This will keep the + // first error (when comparing with many signatures) + if(status == UNDEFINED || this->getStatus() == UNDEFINED || status == ACCEPTED) + { + VerifyHypotheses::setStatus(status); + } +} + +int VerifyHypothesesEpipolarGeo::verifyHypotheses(const std::list & hypotheses, const Memory * mem) +{ + ULOGGER_DEBUG(""); + int hypothesis = 0; + this->setStatus(UNDEFINED); + + if(mem && !hypotheses.empty()) + { + const KeypointSignature * ssRef = dynamic_cast(mem->getLastSignature()); + if(ssRef) + { + unsigned int i=0; + for(std::list::const_iterator iter = hypotheses.begin(); iter!=hypotheses.end(); ++iter) + { + if(*iter > 0) + { + const KeypointSignature * ssHyp = dynamic_cast(mem->getSignature(*iter)); + if(ssHyp) + { + if(doEpipolarGeometry(ssHyp, ssRef)) + { + hypothesis = *iter; + break; + } + } + } + ++i; + } + } + } + else if(!mem) + { + this->setStatus(this->MEMORY_IS_NULL); + } + else if(hypotheses.empty()) + { + this->setStatus(this->NO_HYPOTHESIS); + } + + return hypothesis; +} + +bool VerifyHypothesesEpipolarGeo::doEpipolarGeometry(const KeypointSignature * ssA, const KeypointSignature * ssB) +{ + if(ssA == 0 || ssB == 0) + { + this->setStatus(this->NULL_MATCHING_SURF_SIGNATURES); + return false; + } + ULOGGER_DEBUG("id(%d,%d)", ssA->id(), ssB->id()); + + std::list > pairs; + std::list pairsId; + + //bool allPairs = true; + int realPairsCount = 0; + + + realPairsCount = findPairsOne(ssA->getWords(), ssB->getWords(), pairs, pairsId); + ULOGGER_DEBUG("%d %d", pairs.size(), pairsId.size()); + int pairsCount = pairs.size(); + ULOGGER_DEBUG("id(%d,%d) realPairsCount found=%d, pairsCount=%d...", ssA->id(), ssB->id(), realPairsCount, pairsCount); + + int similarities = this->getTotalSimilarities(ssA->getWords(), ssB->getWords()); + + ULOGGER_DEBUG("realPairsCount=%d, " + "test1=%f%%, " + "test2=%f%%, " + "similarities/total=%f%%, " + "realP/similarities=%f%%, " + "(pairs/2)/similarities=%f%%", + realPairsCount, + float(realPairsCount)/(float(ssA->getWords().size() + ssB->getWords().size())/2), + float(pairs.size())/(float(ssA->getWords().size() + ssB->getWords().size())/2), + float(similarities)/float(ssA->getWords().size() + ssB->getWords().size()), + float(realPairsCount) / float(similarities), + float(pairs.size()) / float(similarities)); + if(pairsCount < _matchCountMinAccepted) + { + this->setStatus(this->NOT_ENOUGH_MATCHING_PAIRS); + return false; + } + + //Convert Keypoints to a structure that OpenCV understands + //3 dimensions (Homogeneous vectors) + cv::Mat points1(1, pairs.size(), CV_32FC2); + cv::Mat points2(1, pairs.size(), CV_32FC2); + + float * points1data = points1.ptr(0); + float * points2data = points2.ptr(0); + + // Fill the points here ... + int i=0; + for(std::list >::const_iterator iter = pairs.begin(); + iter != pairs.end(); + ++iter ) + { + points1data[i*2] = (*iter).first.pt.x; + points1data[i*2+1] = (*iter).first.pt.y; + + points2data[i*2] = (*iter).second.pt.x; + points2data[i*2+1] = (*iter).second.pt.y; + + // the output of the correspondences can be easily copied in MatLab + /*if(i==0) + { + ULOGGER_DEBUG("pt x=[%f;%f;1;%d];,xp=[%f;%f;1;%d];", + (*iter).first.pt.x, + (*iter).first.pt.y, + Util::valueAt(pairsId,i), + (*iter).second.pt.x, + (*iter).second.pt.y, + Util::valueAt(pairsId,i)); + } + else + { + ULOGGER_DEBUG("pt x=[x [%f;%f;1;%d]];,xp=[xp [%f;%f;1;%d]];", + (*iter).first.pt.x, + (*iter).first.pt.y, + Util::valueAt(pairsId,i), + (*iter).second.pt.x, + (*iter).second.pt.y, + Util::valueAt(pairsId,i)); + }*/ + ++i; + } + + UTimer timer; + timer.start(); + + // Find the fundamental matrix + cv::vector status; + cv::Mat fundamentalMatrix = cv::findFundamentalMat( + points1, + points2, + status, + CV_FM_RANSAC, + _ransacParam1, + _ransacParam2); + + ULOGGER_DEBUG("Find fundamental matrix (OpenCV) time = %fs", timer.ticks()); + + // Fundamental matrix is valid ? + bool fundMatFound = false; + if(fundamentalMatrix.type() != CV_64FC1) + { + ULOGGER_FATAL("fundamentalMatrix.type() != CV_64FC1"); + } + if(fundamentalMatrix.cols==3 && fundamentalMatrix.rows==3 && + (fundamentalMatrix.at(0,0) != 0.0 || + fundamentalMatrix.at(0,1) != 0.0 || + fundamentalMatrix.at(0,2) != 0.0 || + fundamentalMatrix.at(1,0) != 0.0 || + fundamentalMatrix.at(1,1) != 0.0 || + fundamentalMatrix.at(1,2) != 0.0 || + fundamentalMatrix.at(2,0) != 0.0 || + fundamentalMatrix.at(2,1) != 0.0 || + fundamentalMatrix.at(2,2) != 0.0) ) + + { + fundMatFound = true; + } + + ULOGGER_DEBUG("id(%d,%d) fm_count=%d...", ssA->id(), ssB->id(), fundMatFound); + + if(fundMatFound) + { + std::list > inliers; + std::list inliersId; + + int goodCount = 0; + float total = 0; + std::list > ptsAddedA; + std::list > ptsAddedB; + cv::Mat x(3, 1, fundamentalMatrix.type()); + cv::Mat xp(1, 3, fundamentalMatrix.type()); + int i=0; + for(std::list >::iterator iter=pairs.begin(); iter!=pairs.end(); ++iter) + { + //if(status[i]) + { + if(uContains(ptsAddedA, std::pair((*iter).first.pt.x, (*iter).first.pt.y))) + { + ULOGGER_DEBUG("already added point [%f,%f,1]", (*iter).first.pt.x, (*iter).first.pt.y); + } + else if(uContains(ptsAddedB, std::pair((*iter).second.pt.x, (*iter).second.pt.y))) + { + ULOGGER_DEBUG("already added point [%f,%f,1]", (*iter).second.pt.x, (*iter).second.pt.y); + } + else + { + double * xData = x.ptr(0); + double * xpData = xp.ptr(0); + xData[0] = (*iter).first.pt.x; + xData[1] = (*iter).first.pt.y; + xData[2] = 1; + xpData[0] = (*iter).second.pt.x; + xpData[1] = (*iter).second.pt.y; + xpData[2] = 1; + cv::Mat r = xp * (fundamentalMatrix * x); + //if((r->data.fl[0] < 0 ? -r->data.fl[0]:r->data.fl[0]) < 1000000) + { + // Add only once a pair for the same id, used when a point matches with more than one... + ptsAddedA.push_back(std::pair((*iter).first.pt.x, (*iter).first.pt.y)); + ptsAddedB.push_back(std::pair((*iter).second.pt.x, (*iter).second.pt.y)); + if(status[i]) + { + inliers.push_back(*iter); + inliersId.push_back(uValueAt(pairsId, i)); + goodCount++; + } + //ULOGGER_DEBUG("[%d] status=%d, r->data.fl[0]=%f, Added!", Util::valueAt(pairsId,i), status[i], r.ptr(0)[0]); + } + /*else + { + ULOGGER_DEBUG("status=%d, r->data.fl[0]=%f, Not added!", status->data.ptr[i], r->data.fl[0]); + }*/ + total+=(r.ptr(0)[0] < 0 ? -r.ptr(0)[0]:r.ptr(0)[0]); + } + } + /*else + { + ULOGGER_DEBUG("VHEpipolarGeo::doEpipolarGeometry() status=%d", status[i]); + }*/ + ++i; + } + + ULOGGER_DEBUG("pairs/realPairs=%d/%d -> %d%%, goodCount=%d -> %d%%, good/real = %d%%, totalMean=%f", + pairsCount, + realPairsCount, + int(float(pairsCount)/float(realPairsCount*100)), + goodCount, + int(float(goodCount)/float(pairsCount*100)), + int(float(goodCount)/float(realPairsCount*100)), + total/float(realPairsCount)); + + // Show the fundamental matrix + ULOGGER_DEBUG( + "F = [%f %f %f;%f %f %f;%f %f %f]", + fundamentalMatrix.ptr(0)[0], + fundamentalMatrix.ptr(0)[1], + fundamentalMatrix.ptr(0)[2], + fundamentalMatrix.ptr(0)[3], + fundamentalMatrix.ptr(0)[4], + fundamentalMatrix.ptr(0)[5], + fundamentalMatrix.ptr(0)[6], + fundamentalMatrix.ptr(0)[7], + fundamentalMatrix.ptr(0)[8]); + + if(goodCount < _matchCountMinAccepted) + { + this->setStatus(this->EPIPOLAR_CONSTRAINT_FAILED); + ULOGGER_DEBUG("Epipolar constraint failed A : not enough inliers (%d), min is %d", goodCount, _matchCountMinAccepted); + return false; + } + else + { + this->setStatus(this->ACCEPTED); + return true; + } + } + this->setStatus(this->FUNDAMENTAL_MATRIX_NOT_FOUND); + return false; +} + +/** + * if a=[1 2 3 4 6 6], b=[1 1 2 4 5 6 6], results= [(1,1a) (2,2) (4,4) (6a,6a) (6b,6b)] + * realPairsCount = 5 + */ +int VerifyHypothesesEpipolarGeo::findPairsDirect(const std::multimap & wordsA, + const std::multimap & wordsB, + std::list > & pairs, + std::list & pairsId) +{ + const std::list & ids = uUniqueKeys(wordsA); + std::multimap::const_iterator iterA; + std::multimap::const_iterator iterB; + pairs.clear(); + int realPairsCount = 0; + for(std::list::const_iterator i=ids.begin(); i!=ids.end(); ++i) + { + iterA = wordsA.find(*i); + iterB = wordsB.find(*i); + while(iterA != wordsA.end() && iterB != wordsB.end() && (*iterA).first == (*iterB).first && (*iterA).first == *i) + { + pairsId.push_back(*i); + pairs.push_back(std::pair((*iterA).second, (*iterB).second)); + ++iterA; + ++iterB; + ++realPairsCount; + } + } + return realPairsCount; +} + +/** + * if a=[1 2 3 4 6 6], b=[1 1 2 4 5 6 6], results= [(2,2) (4,4)] + * realPairsCount = 5 + */ +int VerifyHypothesesEpipolarGeo::findPairsOne(const std::multimap & wordsA, + const std::multimap & wordsB, + std::list > & pairs, + std::list & pairsId) +{ + const std::list & ids = uUniqueKeys(wordsA); + int realPairsCount = 0; + pairs.clear(); + for(std::list::const_iterator i=ids.begin(); i!=ids.end(); ++i) + { + std::list ptsA = uValues(wordsA, *i); + std::list ptsB = uValues(wordsB, *i); + if(ptsA.size() == 1 && ptsB.size() == 1) + { + pairs.push_back(std::pair(ptsA.front(), ptsB.front())); + pairsId.push_back(*i); + ++realPairsCount; + } + else if(ptsA.size()>1 && ptsB.size()>1) + { + // just update the count + realPairsCount += ptsA.size() > ptsB.size() ? ptsB.size() : ptsA.size(); + } + } + return realPairsCount; +} + +/** + * if a=[1 2 3 4 6 6], b=[1 1 2 4 5 6 6], results= [(1,1a) (1,1b) (2,2) (4,4) (6a,6a) (6a,6b) (6b,6a) (6b,6b)] + * realPairsCount = 5 + */ +int VerifyHypothesesEpipolarGeo::findPairsAll(const std::multimap & wordsA, + const std::multimap & wordsB, + std::list > & pairs, + std::list & pairsId) +{ + UTimer timer; + timer.start(); + const std::list & ids = uUniqueKeys(wordsA); + pairs.clear(); + int realPairsCount = 0;; + for(std::list::const_iterator iter=ids.begin(); iter!=ids.end(); ++iter) + { + std::list ptsA = uValues(wordsA, *iter); + std::list ptsB = uValues(wordsB, *iter); + + realPairsCount += ptsA.size() > ptsB.size() ? ptsB.size() : ptsA.size(); + + for(std::list::iterator jter=ptsA.begin(); jter!=ptsA.end(); ++jter) + { + for(std::list::iterator kter=ptsB.begin(); kter!=ptsB.end(); ++kter) + { + pairsId.push_back(*iter); + pairs.push_back(std::pair(*jter, *kter)); + } + } + } + ULOGGER_DEBUG("time = %f", timer.ticks()); + return realPairsCount; +} + +/** + * if a=[1 2 3 4 6 6], b=[1 1 2 4 5 6 6], results= [1 2 4 6] + * return 4 + */ +std::list VerifyHypothesesEpipolarGeo::findSameIds(const std::multimap & wordsA, + const std::multimap & wordsB) +{ + std::list sameIds; + const std::list & ids = uUniqueKeys(wordsA); + for(std::list::const_iterator i=ids.begin(); i!=ids.end(); ++i) + { + if(wordsB.find(*i) != wordsB.end()) + { + sameIds.push_back(*i); + } + } + return sameIds; +} + +int VerifyHypothesesEpipolarGeo::getTotalSimilarities(const std::multimap & wordsA, const std::multimap & wordsB) +{ + const std::list & ids = uUniqueKeys(wordsA); + int total = 0; + for(std::list::const_iterator i=ids.begin(); i!=ids.end(); ++i) + { + total += uValues(wordsA, *i).size(); + total += uValues(wordsB, *i).size(); + } + return total; +} + +} diff --git a/corelib/src/VerifyHypotheses.h b/corelib/src/VerifyHypotheses.h new file mode 100644 index 0000000000..9056030723 --- /dev/null +++ b/corelib/src/VerifyHypotheses.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef VERIFYHYPOTHESES_H_ +#define VERIFYHYPOTHESES_H_ + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include +#include "rtabmap/core/Parameters.h" +#include "utilite/UEventsHandler.h" +#include +#include +#include + +namespace rtabmap +{ + +class Memory; + +class RTABMAP_EXP VerifyHypotheses +{ +public: + virtual ~VerifyHypotheses() {} + virtual int verifyHypotheses(const std::list & hypotheses, const Memory * mem) = 0; + + int getStatus() {return _status;} + virtual void parseParameters(const ParametersMap & parameters); + +protected: + VerifyHypotheses(const ParametersMap & parameters = ParametersMap()); + virtual void setStatus(int status) {_status = status;} + +private: + int _status; +}; + + +///////////////////////// +// VerifyHypothesesSimple +///////////////////////// +class VerifyHypothesesSimple : public VerifyHypotheses { +public: + VerifyHypothesesSimple(const ParametersMap & parameters = ParametersMap()); + virtual ~VerifyHypothesesSimple(); + virtual int verifyHypotheses(const std::list & hypotheses, const Memory * mem); + virtual void parseParameters(const ParametersMap & parameters); +}; + + +///////////////////////// +// VerifyHypothesesSignSeq +///////////////////////// +/*class VerifyHypothesesSignSeq : public VerifyHypotheses { +public: + VerifyHypothesesSignSeq(const ParametersMap & parameters = ParametersMap()); + virtual ~VerifyHypothesesSignSeq(); + virtual int verifyHypotheses(const std::list & hypotheses, const Memory * mem); + virtual void parseParameters(const ParametersMap & parameters); + +private: + std::map _hypotheses; + int _seqLength; + +};*/ + + + +///////////////////////// +// VerifyHypothesesEpipolarGeo +///////////////////////// +class KeypointSignature; + +class RTABMAP_EXP VerifyHypothesesEpipolarGeo : public VerifyHypotheses +{ +public: + enum STATUS + { + UNDEFINED, + ACCEPTED, + NO_HYPOTHESIS, + MEMORY_IS_NULL, + NOT_ENOUGH_MATCHING_PAIRS, + EPIPOLAR_CONSTRAINT_FAILED, + NULL_MATCHING_SURF_SIGNATURES, + FUNDAMENTAL_MATRIX_NOT_FOUND + }; + +public: + static int findPairsOne(const std::multimap & wordsA, const std::multimap & wordsB, std::list > & pairs, std::list & pairsId); + static int findPairsDirect(const std::multimap & wordsA, const std::multimap & wordsB, std::list > & pairs, std::list & pairsId); + static int findPairsAll(const std::multimap & wordsA, const std::multimap & wordsB, std::list > & pairs, std::list & pairsId); + static std::list findSameIds(const std::multimap & wordsA, const std::multimap & wordsB); +public: + VerifyHypothesesEpipolarGeo(const ParametersMap & parameters = ParametersMap()); + virtual ~VerifyHypothesesEpipolarGeo(); + virtual int verifyHypotheses(const std::list & hypotheses, const Memory * mem); + virtual void parseParameters(const ParametersMap & parameters); + + int getTotalSimilarities(const std::multimap & wordsA, const std::multimap & wordsB); + + int getMatchCountMinAccepted() const {return _matchCountMinAccepted;} + double getRansacParam1() const {return _ransacParam1;} + double getRansacParam2() const {return _ransacParam2;} + + void setMatchCountMinAccepted(int matchCountMinAccepted) {_matchCountMinAccepted = matchCountMinAccepted;} + void setRansacParam1(double ransacParam1) {_ransacParam1 = ransacParam1;} + void setRansacParam2(double ransacParam2) {_ransacParam2 = ransacParam2;} + +protected: + virtual void setStatus(int status); + +private: + bool doEpipolarGeometry(const KeypointSignature * ssA, const KeypointSignature * ssB); + +private: + int _matchCountMinAccepted; + double _ransacParam1; + double _ransacParam2; +}; + +} // namespace rtabmap + +#endif /* VERIFYHYPOTHESES_H_ */ diff --git a/corelib/src/VisualWord.cpp b/corelib/src/VisualWord.cpp new file mode 100644 index 0000000000..cf65d5d0b0 --- /dev/null +++ b/corelib/src/VisualWord.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "VisualWord.h" +#include "utilite/ULogger.h" +#include "utilite/UStl.h" + +namespace rtabmap +{ + +VisualWord::VisualWord(int id, const float * descriptor, unsigned int dim, int signatureId) : + _id(id), + _saved(false), + _totalReferences(0) +{ + _descriptor = new float[dim]; + if(_descriptor && descriptor) + { + memcpy(_descriptor, descriptor, dim*sizeof(float)); + } + else + { + ULOGGER_ERROR("not enough memory to create the descriptor..."); + } + _dim = dim; + + if(signatureId) + { + addRef(signatureId); + } +} + +VisualWord::~VisualWord() +{ + if(_descriptor) + { + delete [] _descriptor; + } +} + +void VisualWord::addRef(int signatureId) +{ + std::map::iterator iter = _references.find(signatureId); + if(iter != _references.end()) + { + (*iter).second += 1; + } + else + { + _references.insert(std::pair(signatureId, 1)); + } + ++_totalReferences; +} + +int VisualWord::removeAllRef(int signatureId) +{ + int removed = uTake(_references, signatureId, 0); + _totalReferences -= removed; + return removed; +} + +} // namespace rtabmap diff --git a/corelib/src/VisualWord.h b/corelib/src/VisualWord.h new file mode 100644 index 0000000000..4a2e55ba33 --- /dev/null +++ b/corelib/src/VisualWord.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#pragma once + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include + +namespace rtabmap +{ + +class SignatureSurf; + +class RTABMAP_EXP VisualWord +{ +public: + VisualWord(int id, const float * descriptor, unsigned int dim, int signatureId = 0); + ~VisualWord(); + + void addRef(int signatureId); + int removeAllRef(int signatureId); + + int getTotalReferences() const {return _totalReferences;} + int id() const {return _id;} + const float * getDescriptor() const {return _descriptor;} + unsigned int getDim() const {return _dim;} + const std::map & getReferences() const {return _references;} // (signature id , occurrence in the signature) + + bool isSaved() const {return _saved;} + void setSaved(bool saved) {_saved = saved;} + +private: + int _id; + float * _descriptor; + unsigned int _dim; + bool _saved; // If it's saved to bd + + int _totalReferences; + std::map _references; // (signature id , occurrence in the signature) + std::map _oldReferences; // (signature id , occurrence in the signature) +}; + +} // namespace rtabmap diff --git a/corelib/src/resources/DatabaseSchema.sql b/corelib/src/resources/DatabaseSchema.sql new file mode 100644 index 0000000000..23f8554b3f --- /dev/null +++ b/corelib/src/resources/DatabaseSchema.sql @@ -0,0 +1,198 @@ +-- ******************************************************************* +-- construct_avpd_db: Script for creating the database +-- Usage: +-- $ sqlite3 AvpdDatabase.db < DatabaseSchema.sql +-- +-- ******************************************************************* + +-- ******************************************************************* +-- CLEAN +-- ******************************************************************* +/*DROP TABLE Signature; +DROP TABLE SignatureType; +DROP TABLE Neighbor; +DROP TABLE VisualWord; +DROP TABLE Map_SS_VW; +DROP TABLE StatisticsAfterRun; +DROP TABLE StatisticsAfterRunSurf;*/ + +-- ******************************************************************* +-- CREATE +-- ******************************************************************* +CREATE TABLE Signature ( + id INTEGER NOT NULL, + type VARCHAR NOT NULL, + weight INTEGER, + loopClosureId INTEGER, + image BLOB, + imgWidth INTEGER, + imgHeight INTEGER, + timeEnter DATE, + PRIMARY KEY (id), + FOREIGN KEY (type) REFERENCES SignatureType(type), + FOREIGN KEY (loopClosureId) REFERENCES Signature(id) +); + +CREATE TABLE Neighbor ( + sid INTEGER NOT NULL, + nid INTEGER NOT NULL, + actionSize INTEGER, + actions BLOB, + timeEnter DATE, + PRIMARY KEY (sid, nid), + FOREIGN KEY (sid) REFERENCES Signature(id), + FOREIGN KEY (nid) REFERENCES Signature(id) +); + +CREATE TABLE SignatureType ( + type VARCHAR NOT NULL, + PRIMARY KEY (type) +); + +CREATE TABLE VisualWord ( + id INTEGER NOT NULL, + descriptorSize INTEGER NOT NULL, + descriptor BLOB NOT NULL, + timeEnter DATE, + PRIMARY KEY (id) +); + +CREATE TABLE Map_SS_VW ( + signatureId INTEGER NOT NULL, + visualWordId INTEGER NOT NULL, + pos_x FLOAT NOT NULL, + pos_y FLOAT NOT NULL, + laplacian INTEGER NOT NULL, + size INTEGER NOT NULL, + dir FLOAT NOT NULL, + hessian FLOAT NOT NULL, + timeEnter DATE, + FOREIGN KEY (signatureId) REFERENCES Signature(id), + FOREIGN KEY (visualWordId) REFERENCES VisualWord(id) +); + +CREATE TABLE StatisticsAfterRun ( + stMemSize INTEGER, + lastSignAdded INTEGER, + processMemUsed INTEGER, + databaseMemUsed INTEGER, + timeEnter DATE +); + +CREATE TABLE StatisticsAfterRunSurf ( + dictionarySize INTEGER, + timeEnter DATE +); + +-- ******************************************************************* +-- TRIGGERS +-- ******************************************************************* +CREATE TRIGGER insert_Signature BEFORE INSERT ON Signature +WHEN NOT EXISTS (SELECT type FROM SignatureType WHERE SignatureType.type = NEW.type) +BEGIN + SELECT RAISE(ABORT, 'Foreign key constraint failed'); +END; + +CREATE TRIGGER insert_Neighbor BEFORE INSERT ON Neighbor +WHEN NOT EXISTS (SELECT id FROM Signature WHERE Signature.id = NEW.sid) +BEGIN + SELECT RAISE(ABORT, 'Foreign key constraint failed'); +END; + +CREATE TRIGGER insert_Map_SS_VW BEFORE INSERT ON Map_SS_VW +WHEN NOT EXISTS (SELECT type FROM Signature WHERE Signature.id = NEW.signatureId AND type='surf') +--OR NOT EXISTS (SELECT id FROM VisualWord WHERE VisualWord.id = NEW.visualWordId) +BEGIN + SELECT RAISE(ABORT, 'Foreign key constraint failed'); +END; + + -- Creating a trigger for timeEnter +CREATE TRIGGER insert_Signature_timeEnter AFTER INSERT ON Signature +BEGIN + UPDATE Signature SET timeEnter = DATETIME('NOW') WHERE rowid = new.rowid; +END; + +CREATE TRIGGER insert_Neighbor_timeEnter AFTER INSERT ON Neighbor +BEGIN + UPDATE Neighbor SET timeEnter = DATETIME('NOW') WHERE rowid = new.rowid; +END; + +CREATE TRIGGER insert_VisualWord_timeEnter AFTER INSERT ON VisualWord +BEGIN + UPDATE VisualWord SET timeEnter = DATETIME('NOW') WHERE rowid = new.rowid; +END; + +CREATE TRIGGER insert_Map_SS_VW_timeEnter AFTER INSERT ON Map_SS_VW +BEGIN + UPDATE Map_SS_VW SET timeEnter = DATETIME('NOW') WHERE rowid = new.rowid; +END; + +CREATE TRIGGER insert_StatisticsAfterRun_timeEnter AFTER INSERT ON StatisticsAfterRun +BEGIN + UPDATE StatisticsAfterRun SET timeEnter = DATETIME('NOW') WHERE rowid = new.rowid; +END; + +CREATE TRIGGER insert_StatisticsAfterRunSurf_timeEnter AFTER INSERT ON StatisticsAfterRunSurf +BEGIN + UPDATE StatisticsAfterRunSurf SET timeEnter = DATETIME('NOW') WHERE rowid = new.rowid; +END; + + +-- ******************************************************************* +-- INDEXES +-- ******************************************************************* +CREATE INDEX IDX_Map_SS_VW_SignatureId on Map_SS_VW (signatureId); +CREATE INDEX IDX_Map_SS_VW_VisualWordId on Map_SS_VW (visualWordId); +CREATE INDEX IDX_Signature_Id on Signature (id); +CREATE INDEX IDX_VisualWord_Id on VisualWord (id); +CREATE INDEX IDX_Signature_TimeEnter on Signature (timeEnter); +CREATE INDEX IDX_VisualWord_TimeEnter on VisualWord (timeEnter); +CREATE INDEX IDX_Neighbor_Sid on Neighbor (sid); + +-- ******************************************************************* +-- Data +-- ******************************************************************* +INSERT INTO SignatureType(type) VALUES ('fourier'); +INSERT INTO SignatureType(type) VALUES ('surf'); + +-- ******************************************************************* +-- TESTS +-- ******************************************************************* +-- *** Data Test *** +/* +INSERT INTO Signature VALUES(1, 'surf', null, null, null); +INSERT INTO Signature VALUES(2, 'surf', null, null, null); +INSERT INTO Signature VALUES(3, 'surf', null, null, null); +INSERT INTO VisualWord VALUES (1, 1, 2,'0.213213 0.4352323', null); +INSERT INTO VisualWord VALUES (2, 1, 2,'0.213213 0.4352323', null); +INSERT INTO VisualWord VALUES (3, 3, 2,'0.213213 0.4352323', null); +INSERT INTO Map_SS_VW VALUES (1, 1, 0,0,0,0,0, null); +INSERT INTO Map_SS_VW VALUES (2, 1, 0,0,0,0,0, null); +INSERT INTO Map_SS_VW VALUES (2, 2, 0,0,0,0,0, null); +*/ + +/* +-- For loading words +SELECT vw.id, vw.laplacian, vw.descriptorSize, vw.descriptor, m.signatureId FROM VisualWord as vw INNER JOIN Map_SS_VW as m on vw.id=m.visualWordId ORDER BY vw.id; +*/ + + +-- Refreshing the dictionary +/*SELECT * FROM Map_SS_VW; +SELECT * FROM VisualWord;*/ +/* +DELETE FROM VisualWord; +INSERT INTO VisualWord VALUES (1, 1, 2,'0.213213 0.4352323', null); +DELETE FROM Map_SS_VW WHERE NOT EXISTS (SELECT id FROM VisualWord WHERE id = Map_SS_VW.visualWordId); +*/ +/*SELECT * FROM Map_SS_VW; +SELECT * FROM VisualWord;*/ + +/* +-- Loading only signatures on the last short time memory based on DATE +INSERT INTO Signature VALUES(4, 'surf', null, null, null); +INSERT INTO Signature VALUES(5, 'surf', 4, null, null); +INSERT INTO Signature VALUES(6, 'surf', null, null, null); +INSERT INTO Map_SS_VW VALUES (4, 1, 0,0,0,0,0, null); +SELECT s.id FROM Signature AS s WHERE s.timeEnter >= (SELECT vw.timeEnter FROM VisualWord AS vw LIMIT 1) AND s.loopClosureId IS NULL; +*/ diff --git a/corelib/tests/CMakeLists.txt b/corelib/tests/CMakeLists.txt new file mode 100644 index 0000000000..ce46a2102a --- /dev/null +++ b/corelib/tests/CMakeLists.txt @@ -0,0 +1,37 @@ + +SET(SRC_FILES + main.cpp + Tests.cpp +) + +SET(INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../include + ${CMAKE_CURRENT_SOURCE_DIR}/../src + ${CPPUNIT_INCLUDE_DIR} + ${UTILITE_INCLUDE_DIR} + ${OpenCV_INCLUDE_DIRS} + ${SQLITE3_INCLUDE_DIR} +) + +SET(LIBRARIES + ${UTILITE_LIBRARY} + ${OpenCV_LIBRARIES} + ${CPPUNIT_LIBRARY} + ${SQLITE3_LIBRARY} +) + +# Make sure the compiler can find include files from our library. +INCLUDE_DIRECTORIES(${INCLUDE_DIRS}) + +ADD_DEFINITIONS(${CPPUNIT_DEFINITIONS}) + +# Add binary called "testAvpdCore" that is built from the source file "main.cpp". +# The extension is automatically found. +ADD_EXECUTABLE(testCoreLib ${SRC_FILES}) +TARGET_LINK_LIBRARIES(testCoreLib corelib ${LIBRARIES}) + +SET_TARGET_PROPERTIES( testCoreLib +PROPERTIES + OUTPUT_NAME testCoreLib) + diff --git a/corelib/tests/Tests.cpp b/corelib/tests/Tests.cpp new file mode 100644 index 0000000000..bb603d04b8 --- /dev/null +++ b/corelib/tests/Tests.cpp @@ -0,0 +1,483 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include +#include "Tests.h" + +//Headers for the test BEGIN +#include "rtabmap/core/Camera.h" +#include "Signature.h" +#include "VWDictionary.h" +#include "rtabmap/core/EpipolarGeometry.h" +#include "rtabmap/core/Rtabmap.h" +#include "rtabmap/core/SMState.h" +#include "VerifyHypotheses.h" +#include "rtabmap/core/Parameters.h" +#include "BayesFilter.h" +#include "KeypointMemory.h" + +#include "rtabmap/core/RtabmapEvent.h" +#include "rtabmap/core/CameraEvent.h" + +#include "rtabmap/core/DBDriverFactory.h" +#include "rtabmap/core/DBDriver.h" + +//Util lib +#include "utilite/UtiLite.h" + +//Database +#include + +#include + +//Headers for the test END + +CPPUNIT_TEST_SUITE_REGISTRATION( Tests ); + +using namespace rtabmap; + +void Tests::testAvpd() +{ + printf("\n"); + UTimer timer; + timer.start(); + //Logger::setType(Logger::kTypeConsole); + //Logger::setType(Logger::kTypeFile, "LogTestAvpdCore/testSurfStrategy.txt", false); + //Logger::setLevel(Logger::kDebug); + //Logger::setType(Logger::kTypeInvalid); + + std::string path = "./data/090206-3"; + + CameraImages camera(path); + CPPUNIT_ASSERT_MESSAGE("Camera initialization failed!\n", camera.init()); + + /* Create tasks */ + Rtabmap ctabmap; + ParametersMap parameters; + parameters.insert(ParametersPair(Parameters::kRtabmapSMStateBufferSize(), "0")); + parameters.insert(ParametersPair(Parameters::kDbSqlite3InMemory(), "true")); + parameters.insert(ParametersPair(Parameters::kMemRawDataKept(), "true")); + parameters.insert(ParametersPair(Parameters::kRtabmapPublishStats(), "true")); + parameters.insert(ParametersPair(Parameters::kRtabmapTimeThr(), "0")); + parameters.insert(ParametersPair(Parameters::kSURFHessianThreshold(), "500")); + ctabmap.setWorkingDirectory("./LogTestAvpdCore/"); + ctabmap.init(parameters); + + + /* Start thread's task */ + IplImage * image = 0; + + image = camera.takeImage(); + int imgCount = 0; + while(image) + { + ++imgCount; + printf("Processing image %d/84...\n", imgCount); + ctabmap.process(new SMState(image)); + image = camera.takeImage(); + } + if(image) + { + cvReleaseImage(&image); + } + + CPPUNIT_ASSERT(imgCount == 84); + + ctabmap.dumpData(); + + ULogger::write("testSurfStrategy end..."); + //ULOGGER_INFO("\nTime = %fs", timer.ticks()); + + + + //adjustLikelihood() + std::map likelihood; + likelihood.clear(); + likelihood.insert(std::pair(-1, 0)); + likelihood.insert(std::pair(1, 0)); + likelihood.insert(std::pair(2, 0)); + likelihood.insert(std::pair(3, 0)); + ctabmap.adjustLikelihood(likelihood); + CPPUNIT_ASSERT(likelihood.size() == 4); + std::vector values = uValues(likelihood); + for(unsigned int i=0; i(-1, 0.3)); + likelihood.insert(std::pair(1, 0.4)); + likelihood.insert(std::pair(2, 0.2)); + likelihood.insert(std::pair(3, 0.9)); + ctabmap.adjustLikelihood(likelihood); + CPPUNIT_ASSERT(likelihood.size() == 4); + values = uValues(likelihood); + // Result wanted generated by the TestAdjustLikelihood.m script (MatLab/Tests) + int resultWanted2[4] = {1000,1000,1000,1309}; + for(unsigned int i=0; iopenConnection("LogTestAvpdCore/tmpDatabase.db"); + + delete driver; + + ULogger::write("testSqlite3Database end..."); +} + +// TODO add some tests to test when a signature is forgotten or reactivated +void Tests::testBayesFilter() +{ + //Util::Logger::setLevel(Logger::kDebug); + //Util::Logger::setType(Logger::kTypeConsole); + + BayesFilter bayes; + + //Parameters checks + CPPUNIT_ASSERT( bayes.getVirtualPlacePrior() == Parameters::defaultBayesVirtualPlacePriorThr() ); + CPPUNIT_ASSERT( bayes.getPredictionLCStr().compare(Parameters::defaultBayesPredictionLC()) == 0 ); + + ParametersMap parameters; + parameters.insert(ParametersPair(Parameters::kBayesVirtualPlacePriorThr(), "0.01")); + parameters.insert(ParametersPair(Parameters::kBayesPredictionLC(), "0.01 0.02 0.03 0.04")); + bayes.parseParameters(parameters); + CPPUNIT_ASSERT( uNumber2str(bayes.getVirtualPlacePrior()).compare("0.01") == 0 ); + CPPUNIT_ASSERT( bayes.getPredictionLCStr().compare("0.01 0.02 0.03 0.04") == 0 ); + bayes.setVirtualPlacePrior(-1); + CPPUNIT_ASSERT( bayes.getVirtualPlacePrior() == 0 ); + bayes.setVirtualPlacePrior(1.1); + CPPUNIT_ASSERT( bayes.getVirtualPlacePrior() == 1 ); + bayes.setVirtualPlacePrior(0.6); + CPPUNIT_ASSERT( uNumber2str(bayes.getVirtualPlacePrior()).compare("0.6") == 0 ); + bayes.setPredictionLC(""); + CPPUNIT_ASSERT( bayes.getPredictionLCStr().compare("0.01 0.02 0.03 0.04") == 0 ); + bayes.setPredictionLC("0,01 0,02"); + CPPUNIT_ASSERT( bayes.getPredictionLCStr().compare("0.01 0.02 0.03 0.04") == 0 ); + bayes.setPredictionLC("0.01 0.02"); + CPPUNIT_ASSERT( bayes.getPredictionLCStr().compare("0.01 0.02") == 0 ); + bayes.setPredictionLC("0.01 0.02 0.03"); + CPPUNIT_ASSERT( bayes.getPredictionLCStr().compare("0.01 0.02") == 0 ); + + //reset parameters... + bayes = BayesFilter(); + + //computePosterior() + // Load memory with some data... + KeypointMemory mem; + + //parameters + mem.setCommonSignatureUsed(true); + mem.setMaxStMemSize(1); + bayes.setPredictionLC("0 0.22 0.19 0.25 0.04 0.1 0.02 0.04 0.01 0.01"); + bayes.setVirtualPlacePrior(0.9); + + std::map likelihood; + std::map posterior; + float sum; + likelihood.insert(std::pair(-1, 1)); + int result[100] = {0}; + int ri = 0; + + for(int i=1; i<11; ++i) + { + //ULOGGER_DEBUG("--- %d ---", i); + std::list > memStats; + mem.update(0, memStats); + posterior = bayes.computePosterior(&mem, likelihood); + likelihood.insert(std::pair(i, 1)); + sum = uSum(uValues(posterior)); + CPPUNIT_ASSERT(sum > 1.0-0.0001 && sum < 1.0+0.0001); + for(std::map::const_iterator iter=posterior.begin(); iter!= posterior.end(); ++iter) + { + ULOGGER_DEBUG("%f", (*iter).second); + result[ri++] = int((*iter).second*1000); + } + while((ri) % 10 != 0) + { + result[ri++] = 0; + } + } + // Result wanted generated by the TestBayesFilter.m script (MatLab/Tests) + int resultWanted1[100] = {1000,0,0,0,0,0,0,0,0,0,900,99,0,0,0,0,0,0,0,0,810,113,75,0,0,0,0,0,0,0,729,109,96,64,0,0,0,0,0,0,656,100,98,87,56,0,0,0,0,0,590,93,95,93,78,49,0,0,0,0,531,86,90,92,85,69,42,0,0,0,478,80,86,90,86,77,62,37,0,0,430,75,81,87,86,80,70,55,33,0,387,69,77,83,84,80,73,63,49,29}; + for(int i=0; i<100; ++i) + { + ULOGGER_DEBUG("%d vs %d", result[i], resultWanted1[i]); + CPPUNIT_ASSERT(result[i] >= resultWanted1[i]-1 && result[i] <= resultWanted1[i]+1); + } +} + +void Tests::testKeypointMemory() +{ + //Util::Logger::setLevel(Logger::kInfo); + //Util::Logger::setType(Logger::kTypeConsole); + + KeypointMemory mem; + std::map likelihood; + + ParametersMap parameters; + parameters.insert(ParametersPair(Parameters::kDbSqlite3InMemory(), "false")); // It will be like read-only (database won't be changed in SVN) + parameters.insert(ParametersPair(Parameters::kKpTfIdfLikelihoodUsed(), "true")); + mem.setCommonSignatureUsed(true); + mem.setMaxStMemSize(1); + mem.init("sqlite3", "./data/090206-3.db", false, parameters); + //ULOGGER_INFO("mem.getStMemIds().size() = %d, mem.getStMemIds().size()=%d", mem.getWorkingMemIds().size(), mem.getStMemIds().size()); + CPPUNIT_ASSERT( mem.getWorkingMem().size() + mem.getStMem().size() == 83 ); + + //updateCommonSignature() + const KeypointSignature * virtualPlace = dynamic_cast(mem.getSignature(Memory::kIdVirtual)); + CPPUNIT_ASSERT(virtualPlace != 0); + std::list wordIds = uKeys(virtualPlace->getWords()); + //ULOGGER_INFO("wordIds.size()=%d", wordIds.size()); + // Result wanted generated by the TestUpdateCommonWords.m script (MatLab/Tests) + int commonWordsWanted[163] = {5,8,9,21,22,23,27,30,32,34,36,40,45,53,61,62,64,67,68,77,85,86,97,98,99,100,103,106,122,124,125,127,129,131,143,157,161,164,168,169,172,175,181,182,187,196,197,208,210,217,218,219,220,234,235,237,244,252,286,307,308,310,315,327,328,346,347,350,355,362,372,373,387,393,394,398,404,412,423,428,429,441,442,456,465,470,476,495,496,498,506,520,558,572,584,585,587,596,620,634,637,643,646,647,658,667,668,681,709,721,726,741,766,777,785,790,791,793,808,809,880,896,906,908,919,933,940,954,958,974,981,1002,1023,1026,1058,1060,1071,1074,1097,1146,1152,1166,1215,1217,1229,1334,1341,1387,1478,1510,1539,1549,1566,1601,1624,1649,1693,1814,2046,2141,2424,2442,2674}; + int j=0; + CPPUNIT_ASSERT(wordIds.size() == 163); + for(std::list::iterator i=wordIds.begin(); i!=wordIds.end(); ++i) + { + //ULOGGER_INFO("%d vs %d", *i, commonWordsWanted[j]); + CPPUNIT_ASSERT(*i == commonWordsWanted[j]); + ++j; + } + + //computeLikelihood() + const KeypointSignature * lastSign = dynamic_cast(mem.getLastSignature()); + CPPUNIT_ASSERT(lastSign != 0); + wordIds = uKeys(lastSign->getWords()); + j=0; + int signWordsRequired[136] = {9,24,27,37,39,40,45,46,64,64,67,67,68,68,69,69,79,79,81,85,87,95,109,113,115,117,122,124,127,135,135,139,142,143,145,157,159,161,168,181,188,188,191,192,196,197,208,208,211,213,219,239,241,247,256,257,258,307,310,343,558,587,643,760,896,960,1019,1023,1074,1197,1207,1292,1401,1403,1587,2151,2419,2571,2623,2654,2664,2674,2674,2674,2777,2780,2796,2808,2835,2844,2844,2851,2855,2862,2900,3036,3143,3238,3238,3315,3531,3531,3766,3803,4296,4405,4462,4502,4506,4509,4509,4516,4523,4533,4533,4534,4535,4536,4537,4538,4539,4540,4541,4542,4543,4544,4545,4546,4547,4548,4549,4550,4551,4552,4553,4554}; + CPPUNIT_ASSERT(wordIds.size() == 136); + for(std::list::iterator i=wordIds.begin(); i!=wordIds.end(); ++i) + { + //ULOGGER_INFO("%d vs %d", *i, signWordsRequired[j]); + CPPUNIT_ASSERT(*i == signWordsRequired[j]); + ++j; + } + likelihood = mem.computeLikelihood(lastSign); + //ULOGGER_INFO("likelihood.size() = %d", likelihood.size()); + std::vector values = uValues(likelihood); + int likelihoodWanted[82] = {109,157,263,203,87,66,78,49,60,40,47,43,43,55,102,102,147,0,38,61,64,74,69,103,39,20,44,33,14,14,20,12,18,8,59,19,41,26,45,117,124,173,223,74,0,10,17,53,33,24,33,43,52,68,119,124,146,159,28,68,59,115,71,95,37,18,16,49,9,28,20,9,15,11,10,35,45,73,18,92,167,219}; + CPPUNIT_ASSERT(values.size() == 82); + for(unsigned int i=0; i keypoints; + std::list > descriptors; + unsigned int dim = 2; + std::vector v(dim); + + keypoints.push_back(cv::KeyPoint(cv::Point2f(1,1), 10, 30, 500, 2, 0)); + v[0] = 3; + v[1] = 4; + descriptors.push_back(v); + + dictionary.addNewWords(descriptors, dim, 1); + CPPUNIT_ASSERT(dictionary.getVisualWords().size() == 1); + + //Create a word with the next descriptor (the distance^2 with the first word added = 2) + v[0] = 4; + v[1] = 3; + VisualWord word(2, v.data(), dim, 0); + std::list words; + words.push_back(&word); + std::vector ids; + + //Naive + dictionary.setNNStrategy(VWDictionary::kNNNaive); + dictionary.setMinDistUsed(true); + dictionary.setNndrUsed(false); + dictionary.setMinDist(2.01f); + ids = dictionary.findNN(words); + CPPUNIT_ASSERT(ids.size() == 1); + CPPUNIT_ASSERT(ids.front() == 1); + + dictionary.setMinDistUsed(true); + dictionary.setNndrUsed(false); + dictionary.setMinDist(1.99f); + ids = dictionary.findNN(words); + CPPUNIT_ASSERT(ids.size() == 1); + CPPUNIT_ASSERT(ids.front() == 0); + + + //Opencv kdtree + dictionary.setNNStrategy(VWDictionary::kNNKdTree); + dictionary.setMinDistUsed(true); + dictionary.setNndrUsed(false); + dictionary.setMinDist(2.01f); + ids = dictionary.findNN(words); + CPPUNIT_ASSERT(ids.size() == 1); + CPPUNIT_ASSERT(ids.front() == 1); + + dictionary.setMinDistUsed(true); + dictionary.setNndrUsed(false); + dictionary.setMinDist(1.99f); + ids = dictionary.findNN(words); + CPPUNIT_ASSERT(ids.size() == 1); + CPPUNIT_ASSERT(ids.front() == 0); + + + //FLANN kdtree + dictionary.setNNStrategy(VWDictionary::kNNFlannKdTree); + dictionary.setMinDistUsed(true); + dictionary.setNndrUsed(false); + dictionary.setMinDist(2.01f); + ids = dictionary.findNN(words); + CPPUNIT_ASSERT(ids.size() == 1); + CPPUNIT_ASSERT(ids.front() == 1); + + dictionary.setMinDistUsed(true); + dictionary.setNndrUsed(false); + dictionary.setMinDist(1.99f); + ids = dictionary.findNN(words); + CPPUNIT_ASSERT(ids.size() == 1); + CPPUNIT_ASSERT(ids.front() == 0); +} + +void Tests::testVerifyHypotheses() +{ + std::multimap wordsA; + std::multimap wordsB; + std::list > pairs; + std::list pairsId; + + wordsA.insert(std::pair(1, cv::KeyPoint(0, 0, 1))); + wordsA.insert(std::pair(2, cv::KeyPoint(2, 2, 1))); + wordsA.insert(std::pair(3, cv::KeyPoint(3, 3, 1))); + wordsA.insert(std::pair(4, cv::KeyPoint(4, 4, 1))); + wordsA.insert(std::pair(6, cv::KeyPoint(5, 5, 1))); + wordsA.insert(std::pair(6, cv::KeyPoint(6, 6, 1))); + + wordsB.insert(std::pair(1, cv::KeyPoint(0, 0, 1))); + wordsB.insert(std::pair(1, cv::KeyPoint(1, 1, 1))); + wordsB.insert(std::pair(2, cv::KeyPoint(2, 2, 1))); + wordsB.insert(std::pair(4, cv::KeyPoint(3, 3, 1))); + wordsB.insert(std::pair(5, cv::KeyPoint(4, 4, 1))); + wordsB.insert(std::pair(6, cv::KeyPoint(5, 5, 1))); + wordsB.insert(std::pair(6, cv::KeyPoint(6, 6, 1))); + + int total = VerifyHypothesesEpipolarGeo::findPairsAll(wordsA, wordsB, pairs, pairsId); + + //printf("[%d,%d]\n", total, (int)pairs.size()); + CPPUNIT_ASSERT(total == 5); + CPPUNIT_ASSERT(pairs.size() == 8 && pairsId.size() == 8); + + std::list >::iterator pairsIter=pairs.begin(); + std::list::iterator idIter = pairsId.begin(); + for(;pairsIter!=pairs.end() && idIter != pairsId.end(); ++pairsIter, ++idIter) + { + //printf("(%d)[%f,%f] [%f,%f]\n", *idIter, pairsIter->first.pt.x, pairsIter->first.pt.y, pairsIter->second.pt.x, pairsIter->second.pt.x); + } +} diff --git a/corelib/tests/Tests.h b/corelib/tests/Tests.h new file mode 100644 index 0000000000..77b9d38cb1 --- /dev/null +++ b/corelib/tests/Tests.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef TESTS_H +#define TESTS_H + +#include +#include + +#include + +#include "utilite/UDirectory.h" +#include "utilite/ULogger.h" + +class Tests : public CppUnit::TestFixture { + + CPPUNIT_TEST_SUITE( Tests ); + CPPUNIT_TEST( testAvpd ); + CPPUNIT_TEST( testDBDriverFactory ); + CPPUNIT_TEST( testSqlite3Database ); + CPPUNIT_TEST( testBayesFilter ); + CPPUNIT_TEST( testKeypointMemory ); + CPPUNIT_TEST( testCamera ); + CPPUNIT_TEST( testVWDictionary ); + CPPUNIT_TEST( testVerifyHypotheses ); + CPPUNIT_TEST_SUITE_END(); + +private: +public: + void setUp() + { + if(!UDirectory::exists("./LogTestLibCore")) + { + UDirectory::makeDir("./LogTestLibCore"); + } + + ULogger::reset(); + ULogger::setType(ULogger::kTypeConsole); + ULogger::setLevel(ULogger::kError); + //Util::Logger::setLevel(Util::Logger::kDebug); + } + + void tearDown() + { + ULogger::reset(); + ULogger::setType(ULogger::kTypeConsole); + ULogger::setLevel(ULogger::kError); + //Util::Logger::setLevel(Util::Logger::kDebug); + } + + void testAvpd(); + void testCamera(); + void testDBDriverFactory(); + void testSqlite3Database(); + void testBayesFilter(); + void testKeypointMemory(); + void testVWDictionary(); + void testVerifyHypotheses(); +}; + +#endif diff --git a/corelib/tests/main.cpp b/corelib/tests/main.cpp new file mode 100644 index 0000000000..a3f259b10d --- /dev/null +++ b/corelib/tests/main.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef _MSC_VER +// Use Visual C++'s memory checking functionality +#define _CRTDBG_MAP_ALLOC +#include +#endif // _MSC_VER + +int main( int argc, char **argv) +{ + #ifdef _MSC_VER + //_crtBreakAlloc = 189; + _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); + #endif // _MSC_VER + + //Header of the test + char header[150] = "-------------------------------------\n" + "corelib Test\n" + "-------------------------------------\n"; + CPPUNIT_NS::stdCOut() << header; + + // Create the event manager and test controller + CPPUNIT_NS::TestResult controller; + + // Add a listener that colllects test result + CPPUNIT_NS::TestResultCollector result; + controller.addListener(&result); + + // Add a listener that print dots as test run. + CPPUNIT_NS::BriefTestProgressListener progress; + controller.addListener(&progress); + + // Add the top suite to the test runner + CPPUNIT_NS::TestRunner runner; + runner.addTest(CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest()); + runner.run(controller); + + // Print test in a compiler compatible format. + CPPUNIT_NS::CompilerOutputter outputter( &result, CPPUNIT_NS::stdCOut() ); + outputter.write(); + + // If a path is passed in argument copy the result in it + if (argc == 2) + { + std::ofstream myfile; + myfile.open (argv[1]); + CPPUNIT_NS::CompilerOutputter outputterFile( &result, myfile); + myfile << header; + outputterFile.write(); + myfile.close(); + } + + return 0; +} diff --git a/doc/NewCollege.png b/doc/NewCollege.png new file mode 100644 index 0000000000..eb17e4f3ee Binary files /dev/null and b/doc/NewCollege.png differ diff --git a/guilib/CMakeLists.txt b/guilib/CMakeLists.txt new file mode 100644 index 0000000000..6950ee7706 --- /dev/null +++ b/guilib/CMakeLists.txt @@ -0,0 +1,2 @@ +ADD_SUBDIRECTORY( src ) +ADD_SUBDIRECTORY( ExamplePlot ) \ No newline at end of file diff --git a/guilib/ExamplePlot/CMakeLists.txt b/guilib/ExamplePlot/CMakeLists.txt new file mode 100644 index 0000000000..bee32cab17 --- /dev/null +++ b/guilib/ExamplePlot/CMakeLists.txt @@ -0,0 +1,36 @@ + +ADD_DEFINITIONS(-DPLOT_WIDGET_OUT_OF_LIB) + +### Qt Gui stuff ### +SET(headers_ui + ../src/Plot.h + MainWindow.h +) + +#This will generate moc_* for Qt +QT4_WRAP_CPP(moc_srcs ${headers_ui}) +### Qt Gui stuff end### + +SET(SRC_FILES + ../src/Plot.cpp + main.cpp + ${moc_srcs} +) + +SET(INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/../src +) + +INCLUDE(${QT_USE_FILE}) + +SET(LIBRARIES + ${QT_LIBRARIES} +) + +#include files +INCLUDE_DIRECTORIES(${INCLUDE_DIRS}) + +# create an executable file named "examplePlot" from the source files +ADD_EXECUTABLE(examplePlot ${SRC_FILES}) +# Linking with Qt libraries +TARGET_LINK_LIBRARIES(examplePlot ${LIBRARIES}) \ No newline at end of file diff --git a/guilib/ExamplePlot/MainWindow.h b/guilib/ExamplePlot/MainWindow.h new file mode 100644 index 0000000000..5119678b45 --- /dev/null +++ b/guilib/ExamplePlot/MainWindow.h @@ -0,0 +1,45 @@ + +#ifndef MAINWINDOW_H_ +#define MAINWINDOW_H_ + +#include +#include +#include "Plot.h" + +// Note : compiled with -DPLOT_WIDGET_OUT_OF_LIB to +// remove rtabmap's namespace and UtiLite dependency of Plot. +class MainWindow : public QMainWindow +{ + Q_OBJECT +public: + MainWindow() { + Plot * plot = new Plot(this); + plot->setObjectName("Figure 1"); + plot->setMaxVisibleItems(25); + this->setCentralWidget(plot); // ownership transferred + PlotCurve * curveA = new PlotCurve("Curve A", this); + PlotCurve * curveB = new PlotCurve("Curve B", this); + curveA->setPen(QPen(Qt::red)); + curveB->setPen(QPen(Qt::blue)); + plot->addCurve(curveA); // ownership transferred + plot->addCurve(curveB); // ownership transferred + connect(this, SIGNAL(valueUpdatedA(float)), curveA, SLOT(addValue(float))); + connect(this, SIGNAL(valueUpdatedB(float)), curveB, SLOT(addValue(float))); + connect(&timer_, SIGNAL(timeout()), this, SLOT(updateCounter())); + timer_.start(100); + qsrand(1); + } + ~MainWindow() {} +public slots: + void updateCounter() { + emit valueUpdatedA(qrand() % 100); + emit valueUpdatedB(qrand() % 50); + } +signals: + void valueUpdatedA(float); + void valueUpdatedB(float); +private: + QTimer timer_; +}; + +#endif /* MAINWINDOW_H_ */ diff --git a/guilib/ExamplePlot/main.cpp b/guilib/ExamplePlot/main.cpp new file mode 100644 index 0000000000..5c16750d7a --- /dev/null +++ b/guilib/ExamplePlot/main.cpp @@ -0,0 +1,12 @@ + +#include +#include "MainWindow.h" + +int main(int argc, char *argv[]) { + QApplication app(argc, argv); + MainWindow mainWindow; + mainWindow.show(); + app.connect( &app, SIGNAL( lastWindowClosed() ), + &app, SLOT( quit() ) ); + return app.exec(); +} diff --git a/guilib/Mockup/MainWindow.bmp b/guilib/Mockup/MainWindow.bmp new file mode 100644 index 0000000000..7c78f0cf72 Binary files /dev/null and b/guilib/Mockup/MainWindow.bmp differ diff --git a/guilib/Mockup/MockupAll.pptx b/guilib/Mockup/MockupAll.pptx new file mode 100644 index 0000000000..51c3cd3f7f Binary files /dev/null and b/guilib/Mockup/MockupAll.pptx differ diff --git a/guilib/include/rtabmap/gui/MainWindow.h b/guilib/include/rtabmap/gui/MainWindow.h new file mode 100644 index 0000000000..31d13d2fb2 --- /dev/null +++ b/guilib/include/rtabmap/gui/MainWindow.h @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef MAINWINDOW_H_ +#define MAINWINDOW_H_ + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include "utilite/UEventsHandler.h" +#include +#include +#include "rtabmap/core/RtabmapEvent.h" +#include "rtabmap/gui/PreferencesDialog.h" + +namespace rtabmap { +class Camera; +} + +class QGraphicsScene; +class Ui_mainWindow; +class QActionGroup; + +namespace rtabmap { + +class LikelihoodScene; +class AboutDialog; +class Plot; +class PdfPlotCurve; +class StatsToolBox; +class DetailedProgressDialog; + +class RTABMAP_EXP MainWindow : public QMainWindow, public UEventsHandler +{ + Q_OBJECT + +public: + enum State { + kIdle, + kStartingDetection, + kDetecting, + kPaused, + kMonitoring + }; + + enum SrcType { + kSrcUndefined, + kSrcVideo, + kSrcImages, + kSrcStream + }; + +public: + /** + * @param prefDialog If NULL, a default dialog is created. This + * dialog is automatically destroyed with the MainWindow. + */ + MainWindow(PreferencesDialog * prefDialog = 0, QWidget * parent = 0); + ~MainWindow(); + +public slots: + void changeState(MainWindow::State state); + +protected: + virtual void closeEvent(QCloseEvent* event); + virtual void handleEvent(UEvent* anEvent); + virtual void resizeEvent(QResizeEvent* anEvent); + +private slots: + void startDetection(); + void pauseDetection(); + void stopDetection(); + void generateMap(); + void deleteMemory(); + void openWorkingDirectory(); + void updateEditMenu(); + void selectImages(); + void selectVideo(); + void selectStream(); + void selectDatabase(); + void resetTheMemory(); + void dumpTheMemory(); + void dumpThePrediction(); + void clearTheCache(); + void saveFigures(); + void loadFigures(); + void updateElapsedTime(); + void processStats(const rtabmap::Statistics & stat); + void applyAllPrefSettings(); + void applyPrefSettings(PreferencesDialog::PANEL_FLAGS flags); + void applyPrefSettings(const rtabmap::ParametersMap & parameters); + void processRtabmapEventInit(int status, const QString & info); + void updateItemsShown(); + void changeImgRateSetting(); + void changeTimeLimitSetting(); + void captureScreen(); + +signals: + void statsReceived(const rtabmap::Statistics &); + void thresholdsChanged(int, int); + void stateChanged(MainWindow::State); + void rtabmapEventInitReceived(int status, const QString & info); + void imgRateChanged(double); + void timeLimitChanged(double); + void noMoreImagesReceived(); + void loopClosureThrChanged(float); + void retrievalThrChanged(float); + +private: + void drawKeypoints(const std::multimap & refWords, const std::multimap & loopWords); + void setupMainLayout(bool vertical); + void updateSelectSourceMenu(int type); + +private: + Ui_mainWindow * _ui; + + State _state; + rtabmap::Camera * _camera; + + SrcType _srcType; + QString _srcPath; + + //Dialogs + PreferencesDialog * _preferencesDialog; + AboutDialog * _aboutDialog; + + QSet _lastIds; + int _lastId; + + QMap _imagesMap; + + QTimer * _oneSecondTimer; + QTime * _elapsedTime; + QTime * _logEventTime; + + PdfPlotCurve * _posteriorCurve; + PdfPlotCurve * _likelihoodCurve; + + DetailedProgressDialog * _initProgressDialog; + QActionGroup * _selectSourceGrp; + + QString _graphSavingFileName; +}; + +} + +#endif /* MainWindow_H_ */ diff --git a/guilib/include/rtabmap/gui/PreferencesDialog.h b/guilib/include/rtabmap/gui/PreferencesDialog.h new file mode 100644 index 0000000000..fdc09d3bd3 --- /dev/null +++ b/guilib/include/rtabmap/gui/PreferencesDialog.h @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef PREFERENCESDIALOG_H_ +#define PREFERENCESDIALOG_H_ + +#include "rtabmap/core/RtabmapExp.h" // DLL export/import defines + +#include +#include + +#include "rtabmap/core/Parameters.h" + +class Ui_preferencesDialog; +class QAbstractItemModel; +class QAbstractButton; +class QStandardItemModel; +class QStandardItem; +class QFile; +class QGroupBox; +class QMainWindow; +class QLineEdit; +class QSlider; + +namespace rtabmap { + +class PlotCurve; + +class RTABMAP_EXP PreferencesDialog : public QDialog +{ + Q_OBJECT + +public: + enum PanelFlag { + kPanelDummy = 0, + kPanelGeneralStrategy = 1, + kPanelGeneral = 2, + kPanelFourier = 4, + kPanelSurf = 8, + kPanelSource = 16, + kPanelAll = 31 + }; + // TODO, tried to change the name of PANEL_FLAGS to PanelFlags... but signals/slots errors appeared... + Q_DECLARE_FLAGS(PANEL_FLAGS, PanelFlag); + + enum Src { + kSrcUndef, + kSrcUsbDevice, + kSrcImages, + kSrcVideo, + kSrcDatabase + }; + +public: + static QString getIniFilePath(); + +public: + PreferencesDialog(QWidget * parent = 0); + virtual ~PreferencesDialog(); + + void saveWindowGeometry(const QString & windowName, const QWidget * window); + void loadWindowGeometry(const QString & windowName, QWidget * window); + void saveMainWindowState(const QMainWindow * mainWindow); + void loadMainWindowState(QMainWindow * mainWindow); + + void saveCustomConfig(const QString & section, const QString & key, const QString & value); + QString loadCustomConfig(const QString & section, const QString & key); + + rtabmap::ParametersMap getAllParameters(); + + //General panel + int getGeneralLoggerLevel() const; + int getGeneralLoggerEventLevel() const; + int getGeneralLoggerPauseLevel() const; + int getGeneralLoggerType() const; + bool getGeneralLoggerPrintTime() const; + bool isVerticalLayoutUsed() const; + bool isImageFlipped() const; + bool imageRejectedShown() const; + bool beepOnPause() const; + int getKeypointsOpacity() const; + QString getWorkingDirectory(); + + // source panel + double getGeneralImageRate() const; + bool getGeneralAutoRestart() const; + int getSourceType() const; + QString getSourceTypeStr() const; + int getSourceWidth() const; + int getSourceHeight() const; + QString getSourceImagesPath() const; //Images group + QString getSourceImagesSuffix() const; //Images group + int getSourceImagesSuffixIndex() const; //Images group + int getSourceImagesStartPos() const; //Images group + bool getSourceImagesRefreshDir() const; //Images group + QString getSourceVideoPath() const; //Video group + int getSourceUsbDeviceId() const; //UsbDevice group + QString getSourceDatabasePath() const; //Database group + bool getSourceDatabaseIgnoreChildren() const; //Database group + + // + bool isImagesKept() const; + double getTimeLimit() const; + + //specific + double getLoopThr() const; + double getReacThr() const; + double getVpThr() const; + double getExpThr() const; + +signals: + void settingsChanged(PreferencesDialog::PANEL_FLAGS); + void settingsChanged(rtabmap::ParametersMap); + +public slots: + void setHardThr(int value); + void setReactivationThr(int value); + void setImgRate(double value); + void setAutoRestart(bool value); + void setTimeLimit(double value); + void selectSource(Src src = kSrcUndef); + +private slots: + void closeDialog ( QAbstractButton * button ); + void resetApply ( QAbstractButton * button ); + void resetSettings(int panelNumber); + void loadConfigFrom(); + void saveConfigTo(); + void makeObsoleteGeneralPanel(); + void makeObsoleteSourcePanel(); + void clicked(const QModelIndex &index); + void addParameter(int value); + void addParameter(double value); + void addParameter(const QString & value); + void updatePredictionLCSliders(); + void updatePredictionLC(); + void updateKpROI(); + void changeWorkingDirectory(); + void changeDictionaryPath(); + +protected: + virtual void showEvent ( QShowEvent * event ); + + void setParameter(const std::string & key, const std::string & value); + + virtual QString getParamMessage(); + + virtual void readSettings(const QString & filePath = QString()); + virtual void readGuiSettings(const QString & filePath = QString()); + virtual void readCameraSettings(const QString & filePath = QString()); + virtual void readCoreSettings(const QString & filePath = QString()); + + virtual void writeSettings(const QString & filePath = QString()); + virtual void writeGuiSettings(const QString & filePath = QString()); + virtual void writeCameraSettings(const QString & filePath = QString()); + virtual void writeCoreSettings(const QString & filePath = QString()); + +private: + bool validateForm(); + bool validatePanelGeneral(); + bool validatePanelFourier(); + bool validatePanelSurf(); + void setupTreeView(); + void setupSignals(); + void setupPredictionPanel(); + void setupKpRoiPanel(); + bool parseModel(QList & boxes, QStandardItem * parentItem, int currentLevel, int & absoluteIndex); + void addParameter(const QObject * object, int value); + void addParameter(const QObject * object, double value); + void addParameter(const QObject * object, const QString & value); + void addParameters(const QGroupBox * box); + QList getGroupBoxes(); + +protected: + rtabmap::ParametersMap _parameters; + PANEL_FLAGS _obsoletePanels; + +private: + Ui_preferencesDialog * _ui; + QStandardItemModel * _indexModel; + bool _initialized; + + //For Bayes filter prediction parameters + QList _predictionLCSliders; // Sliders used to setup the prediction + bool _predictionPanelInitialized; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(PreferencesDialog::PANEL_FLAGS) + +} + +#endif /* PREFERENCESDIALOG_H_ */ diff --git a/guilib/src/AboutDialog.cpp b/guilib/src/AboutDialog.cpp new file mode 100644 index 0000000000..7302a22a3f --- /dev/null +++ b/guilib/src/AboutDialog.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "AboutDialog.h" +#include "rtabmap/core/Rtabmap.h" + +namespace rtabmap { + +AboutDialog::AboutDialog(QWidget * parent) : + QDialog(parent) +{ + _ui.setupUi(this); + _ui.label_version->setText(Rtabmap::getVersion().c_str()); +} + +} diff --git a/guilib/src/AboutDialog.h b/guilib/src/AboutDialog.h new file mode 100644 index 0000000000..82858ffb6d --- /dev/null +++ b/guilib/src/AboutDialog.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef ABOUTDIALOG_H_ +#define ABOUTDIALOG_H_ + +#include +#include +#include "ui_aboutDialog.h" + +namespace rtabmap { + +class AboutDialog : public QDialog +{ + Q_OBJECT + +public: + AboutDialog(QWidget * parent = 0); + + virtual ~AboutDialog() {} + +private: + Ui_aboutDialog _ui; +}; + +} + +#endif /* ABOUTDIALOG_H_ */ diff --git a/guilib/src/CMakeLists.txt b/guilib/src/CMakeLists.txt new file mode 100644 index 0000000000..b93aa9be3b --- /dev/null +++ b/guilib/src/CMakeLists.txt @@ -0,0 +1,101 @@ + + +### Qt Gui stuff ### +SET(headers_ui + ../include/${PROJECT_PREFIX}/gui/MainWindow.h + ../include/${PROJECT_PREFIX}/gui/PreferencesDialog.h + ./AboutDialog.h + ./ConsoleWidget.h + ./ImageView.h + ./Plot.h + ./PdfPlot.h + ./StatsToolBox.h + ./DetailedProgressDialog.h +) + +SET(uis + ./ui/mainWindow.ui + ./ui/preferencesDialog.ui + ./ui/aboutDialog.ui + ./ui/consoleWidget.ui +) + +SET(qrc + ./GuiLib.qrc +) + +# generate rules for building source files from the resources +QT4_ADD_RESOURCES(srcs_qrc ${qrc}) + +#Generate .h files from the .ui files +QT4_WRAP_UI(moc_uis ${uis}) + +#This will generate moc_* for Qt +QT4_WRAP_CPP(moc_srcs ${headers_ui}) +### Qt Gui stuff end### + + + + +SET(SRC_FILES + ./MainWindow.cpp + ./PreferencesDialog.cpp + ./KeypointItem.cpp + ./qtipl.cpp + ./ImageView.cpp + ./Plot.cpp + ./PdfPlot.cpp + ./StatsToolBox.cpp + ./DetailedProgressDialog.cpp + ./AboutDialog.cpp + ./ConsoleWidget.cpp + ${moc_srcs} + ${moc_uis} + ${srcs_qrc} +) + +SET(INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/../include + ${CMAKE_CURRENT_SOURCE_DIR} + ${UTILITE_INCLUDE_DIR} + ${OpenCV_INCLUDE_DIRS} + ${CMAKE_CURRENT_BINARY_DIR} # for qt ui generated in binary dir +) + +INCLUDE(${QT_USE_FILE}) + +SET(LIBRARIES + ${UTILITE_LIBRARY} + ${QT_LIBRARIES} + ${OpenCV_LIBS} +) + +#include files +INCLUDE_DIRECTORIES(${INCLUDE_DIRS}) + +IF(WIN32) + IF(BUILD_SHARED_LIBS) + ADD_DEFINITIONS(-DRTABMAP_EXPORTS) + ELSE() + ADD_DEFINITIONS(-DRTABMAP_EXPORTS_STATIC) + ENDIF() +ENDIF(WIN32) + +# create an executable file named "AvpdGui" from the source files +ADD_LIBRARY(guilib ${SRC_FILES}) +# Linking with Qt libraries +TARGET_LINK_LIBRARIES(guilib corelib ${LIBRARIES}) + +SET_TARGET_PROPERTIES( +guilib +PROPERTIES + OUTPUT_NAME ${PROJECT_PREFIX}_gui + INSTALL_NAME_DIR ${CMAKE_INSTALL_PREFIX}/lib +) + +INSTALL(TARGETS guilib + RUNTIME DESTINATION bin COMPONENT runtime + LIBRARY DESTINATION lib COMPONENT devel + ARCHIVE DESTINATION lib COMPONENT devel) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../include/ DESTINATION include/ COMPONENT devel FILES_MATCHING PATTERN "*.h" PATTERN ".svn" EXCLUDE) diff --git a/guilib/src/ConsoleWidget.cpp b/guilib/src/ConsoleWidget.cpp new file mode 100644 index 0000000000..e637406255 --- /dev/null +++ b/guilib/src/ConsoleWidget.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "ConsoleWidget.h" +#include +#include +#include + +namespace rtabmap { + +ConsoleWidget::ConsoleWidget(QWidget * parent) : + QWidget(parent) +{ + _ui.setupUi(this); + UEventsManager::addHandler(this); + connect(this, SIGNAL(msgReceived(const QString &, int)), this, SLOT(appendMsg(const QString &, int))); + _ui.textEdit->document()->setMaximumBlockCount(100); + _ui.textEdit->setFontPointSize(10); +} + +ConsoleWidget::~ConsoleWidget() +{ +} + +void ConsoleWidget::handleEvent(UEvent * anEvent) +{ + // WARNING, don't put a log message here! otherwise it could be recursively called. + if(anEvent->getClassName().compare("ULogEvent") == 0) + { + ULogEvent * logEvent = (ULogEvent*)anEvent; + emit msgReceived(logEvent->getMsg().c_str(), logEvent->getCode()); + if(logEvent->getCode() == ULogger::kFatal) + { + //The application will exit, so warn the user. + QMessageBox::critical(this, tr("Fatal error occurred"), tr("Error! :\n \"%1\"\nThe application will now exit...").arg(logEvent->getMsg().c_str()), QMessageBox::Ok); + } + } +} + +void ConsoleWidget::appendMsg(const QString & msg, int level) +{ + switch(level) + { + case 0: + _ui.textEdit->setTextColor(Qt::darkGreen); + break; + case 2: + _ui.textEdit->setTextColor(Qt::darkYellow); + break; + case 3: + case 4: + _ui.textEdit->setTextColor(Qt::darkRed); + break; + default: + _ui.textEdit->setTextColor(Qt::black); + break; + } + _ui.textEdit->append(msg); +} + +} diff --git a/guilib/src/ConsoleWidget.h b/guilib/src/ConsoleWidget.h new file mode 100644 index 0000000000..0b4d6a50f8 --- /dev/null +++ b/guilib/src/ConsoleWidget.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef CONSOLEWIDGET_H_ +#define CONSOLEWIDGET_H_ + +#include "ui_consoleWidget.h" +#include + +namespace rtabmap { + +class ConsoleWidget : public QWidget, public UEventsHandler +{ + Q_OBJECT; + +public: + ConsoleWidget(QWidget * parent = 0); + virtual ~ConsoleWidget(); + +public slots: + void appendMsg(const QString & msg, int level); + +signals: + void msgReceived(const QString &, int); + +protected: + virtual void handleEvent(UEvent * anEvent); + +private: + Ui_consoleWidget _ui; + +}; + +} + +#endif /* CONSOLEWIDGET_H_ */ diff --git a/guilib/src/DetailedProgressDialog.cpp b/guilib/src/DetailedProgressDialog.cpp new file mode 100644 index 0000000000..770b949264 --- /dev/null +++ b/guilib/src/DetailedProgressDialog.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "DetailedProgressDialog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "utilite/ULogger.h" + +namespace rtabmap { + +DetailedProgressDialog::DetailedProgressDialog(QWidget *parent, Qt::WindowFlags flags) : + QDialog(parent, flags), + _autoClose(false), + _delayedClosingTime(0) +{ + _text = new QLabel(this); + _text->setWordWrap(true); + _progressBar = new QProgressBar(this); + _progressBar->setMaximum(1); + _detailedText = new QPlainTextEdit(this); + _detailedText->setReadOnly(true); + _detailedText->setLineWrapMode(QPlainTextEdit::NoWrap); + _closeButton = new QPushButton(this); + _closeButton->setText("Close"); + _closeWhenDoneCheckBox = new QCheckBox(this); + _closeWhenDoneCheckBox->setChecked(false); + _closeWhenDoneCheckBox->setText("Close when done."); + _endMessage = "Finished!"; + this->clear(); + connect(_closeButton, SIGNAL(clicked()), this, SLOT(close())); + + QVBoxLayout * layout = new QVBoxLayout(this); + layout->addWidget(_text); + layout->addWidget(_progressBar); + layout->addWidget(_detailedText); + QHBoxLayout * hLayout = new QHBoxLayout(); + layout->addLayout(hLayout); + hLayout->addWidget(_closeWhenDoneCheckBox); + hLayout->addWidget(_closeButton); + this->setLayout(layout); +} + +DetailedProgressDialog::~DetailedProgressDialog() +{ + +} + +void DetailedProgressDialog::setAutoClose(bool on, int delayedClosingTimeSec) +{ + _delayedClosingTime = delayedClosingTimeSec; + _closeWhenDoneCheckBox->setChecked(on); +} + +void DetailedProgressDialog::appendText(const QString & text) +{ + _text->setText(text); + _detailedText->appendPlainText(text); +} +void DetailedProgressDialog::setValue(int value) +{ + _progressBar->setValue(value); + if(value == _progressBar->maximum()) + { + _text->setText(_endMessage); + _closeButton->setEnabled(true); + if(_closeWhenDoneCheckBox->isChecked() && _delayedClosingTime == 0) + { + this->close(); + } + else if(_closeWhenDoneCheckBox->isChecked()) + { + QTimer::singleShot(_delayedClosingTime*1000, this, SLOT(close())); + } + } +} +int DetailedProgressDialog::maximumSteps() const +{ + return _progressBar->maximum(); +} +void DetailedProgressDialog::setMaximumSteps(int steps) +{ + _progressBar->setMaximum(steps); +} + +void DetailedProgressDialog::incrementStep() +{ + //incremental progress bar (if we don't know how many items will be added) + if(_progressBar->value() == _progressBar->maximum()-1) + { + _progressBar->setMaximum(_progressBar->maximum()+1); + } + _progressBar->setValue(_progressBar->value()+1); +} + +void DetailedProgressDialog::clear() +{ + _text->clear(); + _progressBar->reset(); + _detailedText->clear(); + _closeButton->setEnabled(false); +} + +void DetailedProgressDialog::closeEvent(QCloseEvent *event) +{ + if(_progressBar->value() == _progressBar->maximum()) + { + event->accept(); + } + else + { + event->ignore(); + } +} + +} diff --git a/guilib/src/DetailedProgressDialog.h b/guilib/src/DetailedProgressDialog.h new file mode 100644 index 0000000000..ae6e185b60 --- /dev/null +++ b/guilib/src/DetailedProgressDialog.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef DETAILEDPROGRESSDIALOG_H_ +#define DETAILEDPROGRESSDIALOG_H_ + +#include + +class QLabel; +class QPlainTextEdit; +class QProgressBar; +class QPushButton; +class QCheckBox; + +namespace rtabmap { + +class DetailedProgressDialog : public QDialog +{ + Q_OBJECT + +public: + DetailedProgressDialog(QWidget *parent = 0, Qt::WindowFlags flags = 0); + virtual ~DetailedProgressDialog(); + + void setEndMessage(const QString & message) {_endMessage = message;} // Message shown when the progress is finished + void setValue(int value); + int maximumSteps() const; + void setMaximumSteps(int steps); + void setAutoClose(bool on, int delayedClosingTimeMsec = 0); + +protected: + virtual void closeEvent(QCloseEvent * event); + +public slots: + void appendText(const QString & text); + void incrementStep(); + void clear(); + +private: + QLabel * _text; + QPlainTextEdit * _detailedText; + QProgressBar * _progressBar; + QPushButton * _closeButton; + QCheckBox * _closeWhenDoneCheckBox; + QString _endMessage; + bool _autoClose; + int _delayedClosingTime; // sec +}; + +} + +#endif /* DETAILEDPROGRESSDIALOG_H_ */ diff --git a/guilib/src/GuiLib.qrc b/guilib/src/GuiLib.qrc new file mode 100644 index 0000000000..78ce3bf485 --- /dev/null +++ b/guilib/src/GuiLib.qrc @@ -0,0 +1,21 @@ + + + images/Stop1NormalYellow.png + images/PauseNormalRed.png + images/PauseNormal.png + images/Play1Normal.png + images/Pause.ico + images/PauseOnLoop.ico + images/PauseLoopRejected.ico + qss/default.qss + images/Plot16.png + images/Plot48.png + images/RTAB-Map.ico + images/RTAB-Map.png + images/IntRoLab.png + images/IntRoLabSmall.png + images/metal_7280826_512.jpg + images/crosshatch_metal_grille_9280154_150.JPG + resources/PreferencesModel.txt + + diff --git a/guilib/src/ImageView.cpp b/guilib/src/ImageView.cpp new file mode 100644 index 0000000000..f5c0f21cab --- /dev/null +++ b/guilib/src/ImageView.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "ImageView.h" + +#include +#include +#include +#include +#include +#include "utilite/ULogger.h" + +namespace rtabmap { + +ImageView::ImageView(QWidget * parent) : + QGraphicsView(parent), + _zoom(250), + _minZoom(250), + _savedFileName((QDir::homePath()+ "/") + "picture" + ".png") +{ + this->setTransformationAnchor(QGraphicsView::AnchorUnderMouse); + this->setScene(new QGraphicsScene(this)); + connect(this->scene(), SIGNAL(sceneRectChanged(const QRectF &)), this, SLOT(updateZoom())); +} + +ImageView::~ImageView() { + +} + +void ImageView::resetZoom() +{ + _zoom = _minZoom; + this->setDragMode(QGraphicsView::NoDrag); +} + +void ImageView::contextMenuEvent(QContextMenuEvent * e) +{ + QMenu menu(tr(""), this); + QAction * saveImage = menu.addAction(tr("Save picture...")); + + if(menu.exec(e->globalPos()) == saveImage) + { + QString text; +#ifdef QT_SVG_LIB + text = QFileDialog::getSaveFileName(this, tr("Save figure to ..."), _savedFileName, "*.png *.xpm *.jpg *.pdf *.svg"); +#else + text = QFileDialog::getSaveFileName(this, tr("Save figure to ..."), _savedFileName, "*.png *.xpm *.jpg *.pdf"); +#endif + if(!text.isEmpty()) + { + _savedFileName = text; + QImage img(this->sceneRect().width(), this->sceneRect().height(),QImage::Format_ARGB32_Premultiplied); + QPainter p(&img); + this->scene()->render(&p, this->sceneRect(), this->sceneRect()); + img.save(text); + } + } +} + +void ImageView::updateZoom() +{ + qreal scaleRatio = 1; + if(this->scene()) + { + scaleRatio = this->geometry().width()/this->sceneRect().width(); + } + _minZoom = log(scaleRatio)/log(2)*50+250; +} + +void ImageView::wheelEvent(QWheelEvent * e) +{ + if(e->delta() > 0) + { + _zoom += 20; + this->setDragMode(QGraphicsView::ScrollHandDrag); + if(_zoom>=500) + { + _zoom = 500; + } + } + else + { + _zoom -= 20; + if(_zoom<=_minZoom) + { + this->setDragMode(QGraphicsView::NoDrag); + _zoom = _minZoom; + this->fitInView(this->sceneRect(), Qt::KeepAspectRatio); + return; + } + } + + qreal scale = qPow(qreal(2), (_zoom - 250) / qreal(50)); + QMatrix matrix; + matrix.scale(scale, scale); + this->setMatrix(matrix); +} + +} diff --git a/guilib/src/ImageView.h b/guilib/src/ImageView.h new file mode 100644 index 0000000000..a813248351 --- /dev/null +++ b/guilib/src/ImageView.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef IMAGEVIEW_H_ +#define IMAGEVIEW_H_ + +#include +#include + +namespace rtabmap { + +class ImageView : public QGraphicsView { + + Q_OBJECT + +public: + ImageView(QWidget * parent = 0); + virtual ~ImageView(); + + void resetZoom(); + +protected: + virtual void contextMenuEvent(QContextMenuEvent * e); + virtual void wheelEvent(QWheelEvent * e); + +private slots: + void updateZoom(); + +private: + int _zoom; + int _minZoom; + QString _savedFileName; +}; + +} + +#endif /* IMAGEVIEW_H_ */ diff --git a/guilib/src/KeypointItem.cpp b/guilib/src/KeypointItem.cpp new file mode 100644 index 0000000000..3928caca9c --- /dev/null +++ b/guilib/src/KeypointItem.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "KeypointItem.h" + +#include +#include +#include +#include "utilite/ULogger.h" + +namespace rtabmap { + +KeypointItem::KeypointItem(qreal x, qreal y, int r, const QString & info, const QColor & color, QGraphicsItem * parent) : + QGraphicsEllipseItem(x, y, r, r, parent), + _info(info), + _placeHolder(0) +{ + this->setPen(QPen(color)); + this->setBrush(QBrush(color)); + this->setAcceptsHoverEvents(true); + this->setFlag(QGraphicsItem::ItemIsFocusable, true); + _width = pen().width(); +} + +KeypointItem::~KeypointItem() +{ + /*if(_placeHolder) + { + delete _placeHolder; + }*/ +} + +void KeypointItem::showDescription() +{ + if(!_placeHolder) + { + _placeHolder = new QGraphicsRectItem (this); + _placeHolder->setVisible(false); + _placeHolder->setBrush(QBrush(QColor ( 0, 0, 0, 170 ))); // Black transparent background + QGraphicsTextItem * text = new QGraphicsTextItem(_placeHolder); + text->setDefaultTextColor(this->pen().color().rgb()); + text->setPlainText(_info); + _placeHolder->setRect(text->boundingRect()); + } + + + if(_placeHolder->parentItem()) + { + _placeHolder->setParentItem(0); // Make it a to level item + } + QPen pen = this->pen(); + this->setPen(QPen(pen.color(), _width+2)); + _placeHolder->setZValue(this->zValue()+1); + _placeHolder->setPos(this->mapFromScene(0,0)); + _placeHolder->setVisible(true); +} + +void KeypointItem::hideDescription() +{ + if(_placeHolder) + { + _placeHolder->setVisible(false); + } + this->setPen(QPen(pen().color(), _width)); +} + +void KeypointItem::hoverEnterEvent ( QGraphicsSceneHoverEvent * event ) +{ + QGraphicsScene * scene = this->scene(); + if(scene && scene->focusItem() == 0) + { + this->showDescription(); + } + else + { + this->setPen(QPen(pen().color(), _width+2)); + } + QGraphicsEllipseItem::hoverEnterEvent(event); +} + +void KeypointItem::hoverLeaveEvent ( QGraphicsSceneHoverEvent * event ) +{ + if(!this->hasFocus()) + { + this->hideDescription(); + } + QGraphicsEllipseItem::hoverEnterEvent(event); +} + +void KeypointItem::focusInEvent ( QFocusEvent * event ) +{ + this->showDescription(); + QGraphicsEllipseItem::focusInEvent(event); +} + +void KeypointItem::focusOutEvent ( QFocusEvent * event ) +{ + this->hideDescription(); + QGraphicsEllipseItem::focusOutEvent(event); +} + +} diff --git a/guilib/src/KeypointItem.h b/guilib/src/KeypointItem.h new file mode 100644 index 0000000000..529c06ff86 --- /dev/null +++ b/guilib/src/KeypointItem.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef KEYPOINTITEM_H_ +#define KEYPOINTITEM_H_ + +#include +#include +#include +#include + +namespace rtabmap { + +class KeypointItem : public QGraphicsEllipseItem +{ +public: + KeypointItem(qreal x, qreal y, int r, const QString & info, const QColor & color = Qt::green, QGraphicsItem * parent = 0); + virtual ~KeypointItem(); + +protected: + virtual void hoverEnterEvent ( QGraphicsSceneHoverEvent * event ); + virtual void hoverLeaveEvent ( QGraphicsSceneHoverEvent * event ); + virtual void focusInEvent ( QFocusEvent * event ); + virtual void focusOutEvent ( QFocusEvent * event ); + +private: + void showDescription(); + void hideDescription(); + +private: + QString _info; + QGraphicsRectItem * _placeHolder; + int _width; +}; + +} + +#endif /* KEYPOINTITEM_H_ */ diff --git a/guilib/src/MainWindow.cpp b/guilib/src/MainWindow.cpp new file mode 100644 index 0000000000..1efa8b33e8 --- /dev/null +++ b/guilib/src/MainWindow.cpp @@ -0,0 +1,1394 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "rtabmap/gui/MainWindow.h" +#include "ui_mainWindow.h" +#include "rtabmap/core/CameraEvent.h" +#include "rtabmap/core/Camera.h" +#include "qtipl.h" +#include "KeypointItem.h" +#include "AboutDialog.h" +#include "utilite/UtiLite.h" +#include "rtabmap/core/Parameters.h" +#include "ImageView.h" +#include "Plot.h" +#include "PdfPlot.h" +#include "StatsToolBox.h" +#include "DetailedProgressDialog.h" + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_FILE_NAME "LogRtabmap.txt" +#define SHARE_SHOW_LOG_FILE "share/rtabmap/ShowLogs.m" +#define SHARE_IMPORT_FILE "share/rtabmap/importfile.m" + +using namespace rtabmap; + +inline static void initGuiResource() { Q_INIT_RESOURCE(GuiLib); } + + +namespace rtabmap { + +MainWindow::MainWindow(PreferencesDialog * prefDialog, QWidget * parent) : + QMainWindow(parent), + _ui(0), + _state(kIdle), + _camera(0), + _srcType(kSrcUndefined), + _preferencesDialog(0), + _aboutDialog(0), + _lastId(0), + _oneSecondTimer(0), + _elapsedTime(0), + _posteriorCurve(0), + _likelihoodCurve(0) +{ + ULOGGER_DEBUG(""); + + initGuiResource(); + + this->setWindowTitle(tr("Constant-Time Appearance-Based Mapping")); + this->setWindowIconText(tr("RTAB-Map")); + + // Create dialogs + _aboutDialog = new AboutDialog(this); + + _ui = new Ui_mainWindow(); + _ui->setupUi(this); + + //Setup dock widgets position if it is the first time the application is started. + //if(!QFile::exists(PreferencesDialog::getIniFilePath())) + { + _ui->dockWidget_posterior->setVisible(false); + _ui->dockWidget_likelihood->setVisible(false); + _ui->dockWidget_statsV2->setVisible(false); + _ui->dockWidget_console->setVisible(false); + } + + if(prefDialog) + { + _preferencesDialog = prefDialog; + _preferencesDialog->setParent(this, Qt::Dialog); + } + else // Default dialog + { + _preferencesDialog = new PreferencesDialog(this); + } + + // Restore window geometry + _preferencesDialog->loadMainWindowState(this); + setupMainLayout(_preferencesDialog->isVerticalLayoutUsed()); + + // Timer + _oneSecondTimer = new QTimer(this); + _oneSecondTimer->setInterval(1000); + _elapsedTime = new QTime(); + _ui->label_elapsedTime->setText("00:00:00"); + connect(_oneSecondTimer, SIGNAL(timeout()), this, SLOT(updateElapsedTime())); + _logEventTime = new QTime(); + _logEventTime->start(); + + //Graphics scenes + _ui->imageView_source->setBackgroundBrush(QBrush(Qt::black)); + _ui->imageView_loopClosure->setBackgroundBrush(QBrush(Qt::black)); + + _posteriorCurve = new PdfPlotCurve("Posterior", &_imagesMap, this); + _ui->posteriorPlot->addCurve(_posteriorCurve); + _ui->posteriorPlot->showLegend(false); + _ui->posteriorPlot->setFixedYAxis(0,1); + ThresholdCurve * tc; + tc = _ui->posteriorPlot->addThreshold("Loop closure thr", float(_preferencesDialog->getLoopThr())); + connect(this, SIGNAL(loopClosureThrChanged(float)), tc, SLOT(setThreshold(float))); + tc = _ui->posteriorPlot->addThreshold("Retrieval thr", float(_preferencesDialog->getReacThr())); + connect(this, SIGNAL(loopClosureThrChanged(float)), tc, SLOT(setThreshold(float))); + + _likelihoodCurve = new PdfPlotCurve("Likelihood", &_imagesMap, this); + _ui->likelihoodPlot->addCurve(_likelihoodCurve); + _ui->likelihoodPlot->showLegend(false); + + _ui->doubleSpinBox_stats_imgRate->setValue(_preferencesDialog->getGeneralImageRate()); + _ui->doubleSpinBox_stats_timeLimit->setValue(_preferencesDialog->getTimeLimit()); + + _initProgressDialog = new DetailedProgressDialog(this); + _initProgressDialog->setWindowTitle(tr("Initialization")); + _initProgressDialog->setMinimumWidth(400); + _initProgressDialog->setAutoClose(true, 1); + + //connect stuff + connect(_ui->actionExit, SIGNAL(triggered()), this, SLOT(close())); + qRegisterMetaType("MainWindow::State"); + connect(this, SIGNAL(stateChanged(MainWindow::State)), this, SLOT(changeState(MainWindow::State))); + connect(this, SIGNAL(rtabmapEventInitReceived(int, const QString &)), this, SLOT(processRtabmapEventInit(int, const QString &))); + + // Dock Widget view actions (Menu->Window) + _ui->menuShow_view->addAction(_ui->dockWidget_posterior->toggleViewAction()); + _ui->menuShow_view->addAction(_ui->dockWidget_likelihood->toggleViewAction()); + _ui->menuShow_view->addAction(_ui->dockWidget_statsV2->toggleViewAction()); + _ui->menuShow_view->addAction(_ui->dockWidget_console->toggleViewAction()); + QAction * a = _ui->menuShow_view->addAction("Status"); + a->setCheckable(false); + connect(a, SIGNAL(triggered(bool)), _initProgressDialog, SLOT(show())); + + + // connect actions with custom slots + connect(_ui->actionStart, SIGNAL(triggered()), this, SLOT(startDetection())); + connect(_ui->actionPause, SIGNAL(triggered()), this, SLOT(pauseDetection())); + connect(_ui->actionStop, SIGNAL(triggered()), this, SLOT(stopDetection())); + connect(_ui->actionReset_the_memory, SIGNAL(triggered()), this, SLOT(resetTheMemory())); + connect(_ui->actionDump_the_memory, SIGNAL(triggered()), this, SLOT(dumpTheMemory())); + connect(_ui->actionDump_the_prediction_matrix, SIGNAL(triggered()), this, SLOT(dumpThePrediction())); + connect(_ui->actionClear_cache, SIGNAL(triggered()), this, SLOT(clearTheCache())); + connect(_ui->actionAbout, SIGNAL(triggered()), _aboutDialog , SLOT(exec())); + connect(_ui->actionGenerate_map, SIGNAL(triggered()), this , SLOT(generateMap())); + connect(_ui->actionDelete_memory, SIGNAL(triggered()), this , SLOT(deleteMemory())); + connect(_ui->menuEdit, SIGNAL(aboutToShow()), this, SLOT(updateEditMenu())); + +#if defined(Q_WS_MAC) or defined(Q_WS_WIN) + connect(_ui->actionOpen_working_directory, SIGNAL(triggered()), SLOT(openWorkingDirectory())); +#else + _ui->menuEdit->removeAction(_ui->actionOpen_working_directory); +#endif + + //Settings menu + _selectSourceGrp = new QActionGroup(this); + _selectSourceGrp->addAction(_ui->actionStream); + _selectSourceGrp->addAction(_ui->actionImages); + _selectSourceGrp->addAction(_ui->actionVideo); + _selectSourceGrp->addAction(_ui->actionDatabase); + this->updateSelectSourceMenu(_preferencesDialog->getSourceType()); + connect(_ui->actionImages, SIGNAL(triggered()), this, SLOT(selectImages())); + connect(_ui->actionVideo, SIGNAL(triggered()), this, SLOT(selectVideo())); + connect(_ui->actionStream, SIGNAL(triggered()), this, SLOT(selectStream())); + connect(_ui->actionDatabase, SIGNAL(triggered()), this, SLOT(selectDatabase())); + + connect(_ui->actionSave_state, SIGNAL(triggered()), this, SLOT(saveFigures())); + connect(_ui->actionLoad_state, SIGNAL(triggered()), this, SLOT(loadFigures())); + + connect(_ui->actionPreferences, SIGNAL(triggered()), _preferencesDialog, SLOT(exec())); + + // Settings changed + qRegisterMetaType("PreferencesDialog::PANEL_FLAGS"); + connect(_preferencesDialog, SIGNAL(settingsChanged(PreferencesDialog::PANEL_FLAGS)), this, SLOT(applyPrefSettings(PreferencesDialog::PANEL_FLAGS))); + qRegisterMetaType("rtabmap::ParametersMap"); + connect(_preferencesDialog, SIGNAL(settingsChanged(rtabmap::ParametersMap)), this, SLOT(applyPrefSettings(rtabmap::ParametersMap))); + connect(_ui->actionApply_settings_to_the_detector, SIGNAL(triggered()), this, SLOT(applyAllPrefSettings())); + + // more connects... + connect(_ui->doubleSpinBox_stats_imgRate, SIGNAL(editingFinished()), this, SLOT(changeImgRateSetting())); + connect(_ui->doubleSpinBox_stats_timeLimit, SIGNAL(editingFinished()), this, SLOT(changeTimeLimitSetting())); + connect(this, SIGNAL(imgRateChanged(double)), _preferencesDialog, SLOT(setImgRate(double))); + connect(this, SIGNAL(timeLimitChanged(double)), _preferencesDialog, SLOT(setTimeLimit(double))); + connect(_ui->checkBox_showFeatures, SIGNAL(toggled(bool)), this, SLOT(updateItemsShown())); + connect(_ui->checkBox_showFeaturesLoop, SIGNAL(toggled(bool)), this, SLOT(updateItemsShown())); + connect(_ui->checkBox_showImage, SIGNAL(toggled(bool)), this, SLOT(updateItemsShown())); + connect(_ui->checkBox_showImageLoop, SIGNAL(toggled(bool)), this, SLOT(updateItemsShown())); + + // Statistics from the detector + qRegisterMetaType("rtabmap::Statistics"); + connect(this, SIGNAL(statsReceived(rtabmap::Statistics)), this, SLOT(processStats(rtabmap::Statistics))); + + connect(this, SIGNAL(noMoreImagesReceived()), this, SLOT(stopDetection())); + + // Apply state + this->changeState(kIdle); + this->applyPrefSettings(PreferencesDialog::kPanelAll); + + _ui->statsToolBox->setWorkingDirectory(_preferencesDialog->getWorkingDirectory()); +} + +MainWindow::~MainWindow() +{ + if(_state != kIdle) + { + this->stopDetection(); + } + delete _ui; + delete _elapsedTime; +} + +void MainWindow::setupMainLayout(bool vertical) +{ + if(vertical) + { + qobject_cast(_ui->centralwidget->layout())->setDirection(QBoxLayout::TopToBottom); + } + else if(!vertical) + { + qobject_cast(_ui->centralwidget->layout())->setDirection(QBoxLayout::LeftToRight); + } +} + +void MainWindow::closeEvent(QCloseEvent* event) +{ + // Try to close all children + /*QList windows = this->findChildren(); + for(int i=0; iclose()) { + event->setAccepted(false); + return; + } + }*/ + bool processStopped = true; + if(_state != kIdle && _state != kMonitoring) + { + this->stopDetection(); + if(_state != kIdle) + { + processStopped = false; + } + } + + if(processStopped) + { + _ui->statsToolBox->closeFigures(); + + //write settings before quit? + _preferencesDialog->saveMainWindowState(this); + + _ui->dockWidget_likelihood->close(); + _ui->dockWidget_posterior->close(); + _ui->dockWidget_statsV2->close(); + _ui->dockWidget_console->close(); + + if(_camera) + { + UERROR("Camera must be already deleted here!"); + delete _camera; + _camera = 0; + } + event->accept(); + } + else + { + event->ignore(); + } +} + +void MainWindow::handleEvent(UEvent* anEvent) +{ + if(anEvent->getClassName().compare("RtabmapEvent") == 0) + { + RtabmapEvent * rtabmapEvent = (RtabmapEvent*)anEvent; + const Statistics & stats = rtabmapEvent->getStats(); + int highestHypothesisId = int(uValue(stats.data(), Statistics::kLoopHighest_hypothesis_id(), 0.0f)); + int rejectLoopReason = int(uValue(stats.data(), Statistics::kLoopRejected_reason(), 0.0f)); + if((stats.loopClosureId() > 0 && _ui->actionPause_on_match->isChecked()) || + (stats.loopClosureId() == 0 && highestHypothesisId > 0 && _ui->actionPause_when_a_loop_hypothesis_is_rejected->isChecked() && (rejectLoopReason >= 12 || (rejectLoopReason == 3 && uValue(stats.data(), Statistics::kLoopHighest_hypothesis_value(), 0.0f)>=_preferencesDialog->getLoopThr())))) + { + if(_state != kPaused) + { + if(_preferencesDialog->beepOnPause()) + { + QApplication::beep(); + } + this->pauseDetection(); + } + } + Statistics statsCp = stats; + emit statsReceived(statsCp); + } + else if(anEvent->getClassName().compare("RtabmapEventInit") == 0) + { + RtabmapEventInit * rtabmapEventInit = (RtabmapEventInit*)anEvent; + emit rtabmapEventInitReceived(rtabmapEventInit->getStatus(), rtabmapEventInit->getInfo().c_str()); + + } + else if(anEvent->getClassName().compare("CameraEvent") == 0) + { + CameraEvent * cameraEvent = (CameraEvent*)anEvent; + if(cameraEvent->getCode() == CameraEvent::kCodeNoMoreImages) + { + if(_preferencesDialog->beepOnPause()) + { + QApplication::beep(); + } + //_ui->statusbar->showMessage(tr("The source has no more images...")); + emit noMoreImagesReceived(); + } + } + else if(anEvent->getClassName().compare("ULogEvent") == 0) + { + ULogEvent * logEvent = (ULogEvent*)anEvent; + if(logEvent->getCode() >= _preferencesDialog->getGeneralLoggerPauseLevel()) + { + QMetaObject::invokeMethod(_ui->dockWidget_console, "show"); + // The timer prevents multiple calls to pauseDetection() before the state can be changed + if(_state != kPaused && _logEventTime->elapsed() > 1000) + { + _logEventTime->start(); + if(_preferencesDialog->beepOnPause()) + { + QApplication::beep(); + } + pauseDetection(); + } + } + } +} + +void MainWindow::processStats(const rtabmap::Statistics & stat) +{ + ULOGGER_DEBUG(""); + QTime time; + time.start(); + //Affichage des stats et images + if(stat.extended()) + { + ULOGGER_DEBUG(""); + _ui->label_refId->setText(QString("New ID = %1").arg(stat.refImageId())); + int highestHypothesisId = static_cast(uValue(stat.data(), Statistics::kLoopHighest_hypothesis_id(), 0.0f)); + bool refParentId = (int)uValue(stat.data(), Statistics::kParent_id(), 0.0f); + bool highestHypothesisIsSaved = (bool)uValue(stat.data(), Statistics::kHypothesis_reactivated(), 0.0f); + //if(!(_state == kPaused && !_ui->label_matchId->text().isEmpty() && !(stat.loopClosureId()>0 || highestHypothesisId>0))) + { + ULOGGER_DEBUG(""); + // Loop closure info + _ui->imageView_source->scene()->clear(); + _ui->imageView_loopClosure->scene()->clear(); + _ui->imageView_source->resetTransform(); + _ui->imageView_loopClosure->resetTransform(); + + _ui->label_matchId->clear(); + QPixmap refPixmap; + _ui->label_stats_imageNumber->setText(QString::number(stat.refImageId())); + if(stat.refImage()) + { + QImage img = Ipl2QImage(stat.refImage()); + //Only kept if it not added as a child in the core, that + //means it will never used for a loop closure detection + if(_preferencesDialog->isImagesKept()) + { + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + img.save(&buffer, "JPEG"); // writes image into JPEG format + _imagesMap.insert(stat.refImageId(), ba); + } + QRectF sceneRect = img.rect(); + refPixmap = QPixmap::fromImage(img); + _ui->imageView_source->scene()->addPixmap(refPixmap)->setVisible(this->_ui->checkBox_showImage->isChecked()); + _ui->imageView_source->setSceneRect(sceneRect); + _ui->imageView_loopClosure->setSceneRect(sceneRect); + } + ULOGGER_DEBUG(""); + if(stat.loopClosureImage()) + { + QImage img = Ipl2QImage(stat.loopClosureImage()); + //Only kept if it not added as a child in the core, that + //means it will never used for a loop closure detection + if(_preferencesDialog->isImagesKept()) + { + if(stat.loopClosureId()>0 || highestHypothesisId>0) + { + int id = stat.loopClosureId()>0?stat.loopClosureId():highestHypothesisId; + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + img.save(&buffer, "JPEG"); // writes image into JPEG format + _imagesMap.insert(id, ba); + } + } + } + ULOGGER_DEBUG(""); + _ui->imageView_loopClosure->setBackgroundBrush(QBrush(Qt::black)); + int rejectLoopReason = int(uValue(stat.data(), Statistics::kLoopRejected_reason(), 0.0f)); + if(stat.loopClosureId()>0) + { + _ui->label_stats_loopClosuresDetected->setText(QString::number(_ui->label_stats_loopClosuresDetected->text().toInt() + 1)); + if(highestHypothesisIsSaved) + { + _ui->label_stats_loopClosuresReactivatedDetected->setText(QString::number(_ui->label_stats_loopClosuresReactivatedDetected->text().toInt() + 1)); + } + _ui->label_matchId->setText(QString("Match ID = %1").arg(stat.loopClosureId())); + + QImage img; + QMap::iterator iter = _imagesMap.find(stat.loopClosureId()); + if(iter != _imagesMap.end()) + { + if(!img.loadFromData(iter.value(), "JPEG")) + { + ULOGGER_ERROR("conversion from QByteArray to QImage failed"); + img = QImage(); + } + } + else if(stat.loopClosureImage()) + { + img = Ipl2QImage(stat.loopClosureImage()); + } + + if(!img.isNull()) + { + _ui->imageView_loopClosure->scene()->addPixmap(QPixmap::fromImage(img))->setVisible(this->_ui->checkBox_showImageLoop->isChecked()); + } + } + else if(rejectLoopReason>=12 || + (rejectLoopReason == 3 && uValue(stat.data(), Statistics::kLoopHighest_hypothesis_value(), 0.0f)>=_preferencesDialog->getLoopThr())) + { + ULOGGER_DEBUG(""); + _ui->label_stats_loopClosuresRejected->setText(QString::number(_ui->label_stats_loopClosuresRejected->text().toInt() + 1)); + _ui->label_matchId->setText(QString("Loop hypothesis (%1) rejected!").arg(highestHypothesisId)); + + if(_preferencesDialog->imageRejectedShown()) + { + // rejectLoopReason, + // 1 is for not enough hypotheses, [not supposed to occur if highestHypothesisId is set] + // 10 to ... means rejecting reasons from the hypotheses verification step. + // UNDEFINED, 10 + // ACCEPTED, 11 + // NO_HYPOTHESIS, 12 + // MEMORY_IS_NULL, 13 + // NOT_ENOUGH_MATCHING_PAIRS, 14 + // EPIPOLAR_CONSTRAINT_FAILED, 15 + // NULL_MATCHING_SURF_SIGNATURES 16 + // FUNDAMENTAL_MATRIX_NOT_FOUND 17 + if(rejectLoopReason == 3 || + ((rejectLoopReason-10)>=0 && (rejectLoopReason-10) < 12)) + { + QColor color; + QGraphicsTextItem * textItem = 0; + switch(rejectLoopReason) + { + case 3: + textItem = _ui->imageView_loopClosure->scene()->addText(tr("Rejected by hypothesis ratio")); + color = Qt::yellow; + break; + case 13: + textItem = _ui->imageView_loopClosure->scene()->addText(tr("Memory is null (Intern error)")); + color = Qt::blue; + break; + case 14: + textItem = _ui->imageView_loopClosure->scene()->addText(tr("Not enough matching pairs")); + color = Qt::red; + break; + case 15: + textItem = _ui->imageView_loopClosure->scene()->addText(tr("Epipolar geometry verification failed")); + color = Qt::cyan; + break; + case 16: + textItem = _ui->imageView_loopClosure->scene()->addText(tr("Signatures are not the same type")); + color = Qt::magenta; + break; + case 17: + textItem = _ui->imageView_loopClosure->scene()->addText(tr("Fundamental matrix not found")); + color = Qt::yellow; + break; + default: + textItem = _ui->imageView_loopClosure->scene()->addText(tr("Undefined rejecting reason!?")); + color = Qt::blue; + break; + } + textItem->setDefaultTextColor(QColor(255-color.red(), 255-color.green(), 255-color.blue())); // color inverted + textItem->setZValue(2); + + _ui->imageView_loopClosure->setBackgroundBrush(QBrush(color)); + QImage img; + QMap::iterator iter = _imagesMap.find(highestHypothesisId); + if(iter != _imagesMap.end()) + { + if(!img.loadFromData(iter.value(), "JPEG")) + { + ULOGGER_ERROR("conversion from QByteArray to QImage failed"); + img = QImage(); + } + } + else if(stat.loopClosureImage()) + { + img = Ipl2QImage(stat.loopClosureImage()); + } + + if(!img.isNull()) + { + _ui->imageView_loopClosure->scene()->addPixmap(QPixmap::fromImage(img)); + } + } + else + { + ULOGGER_ERROR("rejectLoopReason=%d-10 is outside of the range of Qt::GlobalColor", rejectLoopReason); + } + } + } + ULOGGER_DEBUG(""); + if((stat.loopClosureId()>0 || rejectLoopReason>=12) && highestHypothesisIsSaved) + { + _ui->label_matchId->setText(QString("[Retrieved!] ").append(_ui->label_matchId->text())); + } + + ULOGGER_DEBUG(""); + if(_ui->imageView_loopClosure->items().size() || stat.loopClosureId()>0) + { + this->drawKeypoints(stat.refWords(), stat.loopWords()); + } + else + { + this->drawKeypoints(stat.refWords(), std::multimap()); //empty loop keypoints... + } + + // We use the reference image to resize the 2 views + _ui->imageView_source->resetZoom(); + _ui->imageView_loopClosure->resetZoom(); + if(!stat.refImage()) + { + _ui->imageView_source->setSceneRect(_ui->imageView_source->scene()->itemsBoundingRect()); + _ui->imageView_loopClosure->setSceneRect(_ui->imageView_source->scene()->itemsBoundingRect()); + } + _ui->imageView_source->fitInView(_ui->imageView_source->sceneRect(), Qt::KeepAspectRatio); + _ui->imageView_loopClosure->fitInView(_ui->imageView_source->sceneRect(), Qt::KeepAspectRatio); + + if(_preferencesDialog->isImageFlipped()) + { + _ui->imageView_source->scale(-1.0, 1.0); + _ui->imageView_loopClosure->scale(-1.0, 1.0); + } + + _ui->statsToolBox->updateStat("Keypoint/Keypoints count in the last signature/", stat.refImageId(), stat.refWords().size()); + _ui->statsToolBox->updateStat("Keypoint/Keypoints count in the loop signature/", stat.refImageId(), stat.loopWords().size()); + ULOGGER_DEBUG(""); + if(!stat.posterior().empty() && _ui->dockWidget_posterior->isVisible()) + { + ULOGGER_DEBUG(""); + if(!refParentId) + { + _posteriorCurve->setData(QMap(stat.posterior()), QMap(stat.weights()), stat.refImageId()); + } + else + { + // if the new signature has a parent, don't add it + _posteriorCurve->setData(QMap(stat.posterior()), QMap(stat.weights()), 0); + } + + ULOGGER_DEBUG(""); + //Adjust thresholds + float value; + value = float(_preferencesDialog->getLoopThr()); + emit(loopClosureThrChanged(value)); + value = float(_preferencesDialog->getReacThr()); + emit(retrievalThrChanged(value)); + } + if(!stat.likelihood().empty() && _ui->dockWidget_likelihood->isVisible()) + { + ULOGGER_DEBUG(""); + if(!refParentId) + { + _likelihoodCurve->setData(QMap(stat.likelihood()), QMap(stat.weights()), stat.refImageId()); + } + else + { + // if the new signature has a parent, don't add it + _likelihoodCurve->setData(QMap(stat.likelihood()), QMap(stat.weights()), 0); + } + ULOGGER_DEBUG(""); + } + + ULOGGER_DEBUG(""); + // Update statistics tool box + const std::map & statistics = stat.data(); + for(std::map::const_iterator iter = statistics.begin(); iter != statistics.end(); ++iter) + { + //ULOGGER_DEBUG("Updating stat \"%s\"", (*iter).first.c_str()); + _ui->statsToolBox->updateStat(QString((*iter).first.c_str()).replace('_', ' '), stat.refImageId(), (*iter).second); + } + } + + } + else if(!stat.extended() && stat.loopClosureId()>0) + { + _ui->label_stats_loopClosuresDetected->setText(QString::number(_ui->label_stats_loopClosuresDetected->text().toInt() + 1)); + _ui->label_matchId->setText(QString("Match ID = %1").arg(stat.loopClosureId())); + } + float elapsedTime = static_cast(time.elapsed()); + ULOGGER_INFO("Processing statistics time = %fs", elapsedTime/1000.0f); + _ui->statsToolBox->updateStat("/Gui refresh stats/ms", stat.refImageId(), elapsedTime); + + this->captureScreen(); +} + +void MainWindow::processRtabmapEventInit(int status, const QString & info) +{ + if((RtabmapEventInit::Status)status == RtabmapEventInit::kInitializing) + { + if(_state == kDetecting) + { + this->pauseDetection(); + } + _initProgressDialog->clear(); + _initProgressDialog->show(); + } + else if((RtabmapEventInit::Status)status == RtabmapEventInit::kInitialized) + { + _initProgressDialog->setValue(_initProgressDialog->maximumSteps()); + } + else + { + _initProgressDialog->incrementStep(); + QString msg(info); + if((RtabmapEventInit::Status)status == RtabmapEventInit::kError) + { + _initProgressDialog->setAutoClose(false); + msg.prepend(tr("[ERROR] ")); + } + _initProgressDialog->appendText(msg); + } +} + +void MainWindow::applyAllPrefSettings() +{ + ULOGGER_DEBUG(""); + + //This will update the statistics toolbox + if(_ui->statsToolBox->findChildren().size() == 0) + { + const std::map & statistics = Statistics::defaultData(); + for(std::map::const_iterator iter = statistics.begin(); iter != statistics.end(); ++iter) + { + _ui->statsToolBox->updateStat(QString((*iter).first.c_str()).replace('_', ' '), 0, (*iter).second); + } + } + + this->applyPrefSettings(PreferencesDialog::kPanelAll); + this->applyPrefSettings(_preferencesDialog->getAllParameters()); +} + +void MainWindow::applyPrefSettings(PreferencesDialog::PANEL_FLAGS flags) +{ + ULOGGER_DEBUG(""); + if(flags & PreferencesDialog::kPanelSource) + { + // Camera settings... + _ui->doubleSpinBox_stats_imgRate->setValue(_preferencesDialog->getGeneralImageRate()); + this->updateSelectSourceMenu(_preferencesDialog->getSourceType()); + _ui->label_stats_source->setText(_selectSourceGrp->checkedAction()->text().replace('.', "")); + + this->post(new CameraEvent(CameraEvent::kCmdChangeParam, _preferencesDialog->getGeneralImageRate(), _preferencesDialog->getGeneralAutoRestart())); + } + + if(flags & PreferencesDialog::kPanelGeneral) + { + ULogger::setLevel((ULogger::Level)_preferencesDialog->getGeneralLoggerLevel()); + ULogger::setEventLevel((ULogger::Level)_preferencesDialog->getGeneralLoggerEventLevel()); + ULogger::setType((ULogger::Type)_preferencesDialog->getGeneralLoggerType(), ((_preferencesDialog->getWorkingDirectory()+"/")+LOG_FILE_NAME).toStdString(), true); + ULogger::setPrintTime(_preferencesDialog->getGeneralLoggerPrintTime()); + setupMainLayout(_preferencesDialog->isVerticalLayoutUsed()); + } +} + +void MainWindow::applyPrefSettings(const rtabmap::ParametersMap & parameters) +{ + ULOGGER_DEBUG(""); + if(parameters.size()) + { + if(_state != kIdle) + { + rtabmap::ParametersMap parametersModified = parameters; + if(parametersModified.erase(Parameters::kRtabmapWorkingDirectory())) + { + if(_state == kMonitoring) + { + QMessageBox::information(this, tr("Working memory changed"), tr("The remote working directory can't be changed while the interface is in monitoring mode.")); + } + else + { + QMessageBox::information(this, tr("Working memory changed"), tr("The working directory can't be changed while the detector is running. This will be applied when the detector will stop.")); + } + } + this->post(new ParamEvent(parametersModified)); + } + else if(parameters.size() == _preferencesDialog->getAllParameters().size()) + { + // Send only if all parameters are sent + this->post(new ParamEvent(parameters)); + } + } + + //update ui + _ui->doubleSpinBox_stats_timeLimit->setValue(_preferencesDialog->getTimeLimit()); + + float value; + value = float(_preferencesDialog->getLoopThr()); + emit(loopClosureThrChanged(value)); + value = float(_preferencesDialog->getReacThr()); + emit(retrievalThrChanged(value)); +} + +void MainWindow::drawKeypoints(const std::multimap & refWords, const std::multimap & loopWords) +{ + UTimer timer; + + timer.start(); + KeypointItem * item = 0; + int alpha = _preferencesDialog->getKeypointsOpacity()*255/100; + ULOGGER_DEBUG("refWords.size() = %d", refWords.size()); + for(std::multimap::const_iterator i = refWords.begin(); i != refWords.end(); ++i ) + { + const cv::KeyPoint & r = (*i).second; + int id = (*i).first; + + QString info = QString( "WordRef = %1\n" + "Laplacian = %2\n" + "Dir = %3\n" + "Hessian = %4\n" + "X = %5\n" + "Y = %6\n" + "Size = %7").arg(id).arg(1).arg(r.angle).arg(r.response).arg(r.pt.x).arg(r.pt.y).arg(r.size); + float radius = r.size*1.2/9.*2; + if(uContains(loopWords, id)) + { + // PINK = FOUND IN LOOP SIGNATURE + item = new KeypointItem(r.pt.x-radius, r.pt.y-radius, radius*2, info, QColor(255, 0, 255, alpha)); + } + else if(_lastIds.contains(id)) + { + // BLUE = FOUND IN LAST SIGNATURE + item = new KeypointItem(r.pt.x-radius, r.pt.y-radius, radius*2, info, QColor(0, 0, 255, alpha)); + } + else if(id<=_lastId) + { + // RED = ALREADY EXISTS + item = new KeypointItem(r.pt.x-radius, r.pt.y-radius, radius*2, info, QColor(255, 0, 0, alpha)); + } + else if(refWords.count(id) > 1) + { + // YELLOW = NEW and multiple times + item = new KeypointItem(r.pt.x-radius, r.pt.y-radius, radius*2, info, QColor(255, 255, 0, alpha)); + } + else + { + // GREEN = NEW + item = new KeypointItem(r.pt.x-radius, r.pt.y-radius, radius*2, info, QColor(0, 255, 0, alpha)); + } + item->setVisible(this->_ui->checkBox_showFeatures->isChecked()); + this->_ui->imageView_source->scene()->addItem(item); + item->setZValue(1); + } + ULOGGER_DEBUG("source time = %f s", timer.ticks()); + + timer.start(); + item = 0; + ULOGGER_DEBUG("loopWords.size() = %d", loopWords.size()); + for(std::multimap::const_iterator i = loopWords.begin(); i != loopWords.end(); ++i ) + { + const cv::KeyPoint & r = (*i).second; + int id = (*i).first; + + QString info = QString( "WordRef = %1\n" + "Laplacian = %2\n" + "Dir = %3\n" + "Hessian = %4\n" + "X = %5\n" + "Y = %6\n" + "Size = %7").arg(id).arg(1).arg(r.angle).arg(r.response).arg(r.pt.x).arg(r.pt.y).arg(r.size); + float radius = r.size*1.2/9.*2; + if(uContains(refWords, id)) + { + // PINK = FOUND IN LOOP SIGNATURE + item = new KeypointItem(r.pt.x-radius, r.pt.y-radius, radius*2, info, QColor(255, 0, 255, alpha)); + } + else if(id<=_lastId) + { + // RED = ALREADY EXISTS + item = new KeypointItem(r.pt.x-radius, r.pt.y-radius, radius*2, info, QColor(255, 0, 0, alpha)); + } + else if(refWords.count(id) > 1) + { + // YELLOW = NEW and multiple times + item = new KeypointItem(r.pt.x-radius, r.pt.y-radius, radius*2, info, QColor(255, 255, 0, alpha)); + } + else + { + // GREEN = NEW + item = new KeypointItem(r.pt.x-radius, r.pt.y-radius, radius*2, info, QColor(0, 255, 0, alpha)); + } + item->setVisible(this->_ui->checkBox_showFeaturesLoop->isChecked()); + this->_ui->imageView_loopClosure->scene()->addItem(item); + item->setZValue(1); + } + ULOGGER_DEBUG("loop closure time = %f s", timer.ticks()); + + if(refWords.size()>0) + { + if((*refWords.rbegin()).first > _lastId) + { + _lastId = (*refWords.rbegin()).first; + } + _lastIds = QSet::fromList(QList::fromStdList(uKeys(refWords))); + } +} + +void MainWindow::updateItemsShown() +{ + QList items = _ui->imageView_source->scene()->items(); + for(int i=0; i(items.at(i))) + { + items.at(i)->setVisible(_ui->checkBox_showFeatures->isChecked()); + } + else if(qgraphicsitem_cast(items.at(i))) + { + items.at(i)->setVisible(_ui->checkBox_showImage->isChecked()); + } + } + + items = _ui->imageView_loopClosure->scene()->items(); + for(int i=0; i(items.at(i))) + { + items.at(i)->setVisible(_ui->checkBox_showFeaturesLoop->isChecked()); + } + else if(qgraphicsitem_cast(items.at(i))) + { + items.at(i)->setVisible(_ui->checkBox_showImageLoop->isChecked()); + } + } +} + +void MainWindow::resizeEvent(QResizeEvent* anEvent) +{ + _ui->imageView_source->fitInView(_ui->imageView_source->sceneRect(), Qt::KeepAspectRatio); + _ui->imageView_loopClosure->fitInView(_ui->imageView_source->sceneRect(), Qt::KeepAspectRatio); + _ui->imageView_source->resetZoom(); + _ui->imageView_loopClosure->resetZoom(); +} + +void MainWindow::updateSelectSourceMenu(int type) +{ + switch(type) + { + case 0: + _ui->actionStream->setChecked(true); + break; + case 1: + _ui->actionImages->setChecked(true); + break; + case 2: + _ui->actionVideo->setChecked(true); + break; + case 3: + default: + _ui->actionDatabase->setChecked(true); + break; + } +} + +void MainWindow::changeImgRateSetting() +{ + emit imgRateChanged(_ui->doubleSpinBox_stats_imgRate->value()); +} + +void MainWindow::changeTimeLimitSetting() +{ + emit timeLimitChanged(_ui->doubleSpinBox_stats_timeLimit->value()); +} + +void MainWindow::captureScreen() +{ + if(!_ui->actionAuto_screen_capture->isChecked()) + { + return; + } + QString targetDir = _preferencesDialog->getWorkingDirectory() + "/ScreensCaptured"; + QDir dir; + if(!dir.exists(targetDir)) + { + dir.mkdir(targetDir); + } + targetDir += "/"; + targetDir += "Main_window"; + if(!dir.exists(targetDir)) + { + dir.mkdir(targetDir); + } + targetDir += "/"; + QString name = QDateTime::currentDateTime().toString("yyMMdd-hhmmsszzz") + ".png"; + _ui->statusbar->clearMessage(); + QPixmap figure = QPixmap::grabWidget(this); + figure.save(targetDir + name); + _ui->statusbar->showMessage(tr("Screen captured \"%1\"").arg(targetDir + name), _preferencesDialog->getTimeLimit()*500); +} + + +//ACTIONS +void MainWindow::startDetection() +{ + emit stateChanged(kStartingDetection); + + if(_camera != 0) + { + QMessageBox::warning(this, + tr("RTAB-Map"), + tr("A camera is running, stop it first.")); + ULogger::write("_camera is not null... stop it first"); + emit stateChanged(kIdle); + return; + } + + // Change type of the camera... + // + int sourceType = _preferencesDialog->getSourceType(); + if(sourceType == 1) //Images + { + _camera = new CameraImages( + _preferencesDialog->getSourceImagesPath().append(QDir::separator()).toStdString(), + _preferencesDialog->getSourceImagesStartPos(), + _preferencesDialog->getSourceImagesRefreshDir(), + _preferencesDialog->getGeneralImageRate(), + _preferencesDialog->getGeneralAutoRestart(), + _preferencesDialog->getSourceWidth(), + _preferencesDialog->getSourceHeight() + ); + } + else if(sourceType == 2) + { + _camera = new CameraVideo( + _preferencesDialog->getSourceVideoPath().toStdString(), + _preferencesDialog->getGeneralImageRate(), + _preferencesDialog->getGeneralAutoRestart(), + _preferencesDialog->getSourceWidth(), + _preferencesDialog->getSourceHeight()); + } + else if(sourceType == 3) + { + _camera = new CameraDatabase( + _preferencesDialog->getSourceDatabasePath().toStdString(), + _preferencesDialog->getSourceDatabaseIgnoreChildren(), + _preferencesDialog->getGeneralImageRate(), + _preferencesDialog->getGeneralAutoRestart(), + _preferencesDialog->getSourceWidth(), + _preferencesDialog->getSourceHeight()); + } + else if(sourceType == 0) + { + _camera = new CameraVideo( + _preferencesDialog->getSourceUsbDeviceId(), + _preferencesDialog->getGeneralImageRate(), + _preferencesDialog->getGeneralAutoRestart(), + _preferencesDialog->getSourceWidth(), + _preferencesDialog->getSourceHeight()); + } + else + { + QMessageBox::warning(this, + tr("RTAB-Map"), + tr("Source type is not supported...")); + ULOGGER_WARN("iSource type not supported..."); + emit stateChanged(kIdle); + return; + } + + _camera->setPostThreatement(new CamKeypointTreatment(_preferencesDialog->getAllParameters())); + + if(!_camera->init()) + { + QMessageBox::warning(this, + tr("RTAB-Map"), + tr("Camera initialization failed...")); + ULOGGER_WARN("init camera failed... "); + emit stateChanged(kIdle); + delete _camera; + _camera = 0; + return; + } + + UEventsManager::addHandler(_camera); //thread + + this->applyAllPrefSettings(); + + _camera->start(); + + emit stateChanged(kDetecting); +} + +void MainWindow::pauseDetection() +{ + ULOGGER_DEBUG(""); + this->post(new CameraEvent(CameraEvent::kCmdPause)); + emit stateChanged(kPaused); +} + +void MainWindow::stopDetection() +{ + if(_state == kIdle) + { + return; + } + if(_state == kDetecting && !_camera->isPaused()) + { + QMessageBox::StandardButton button = QMessageBox::question(this, tr("Stopping process..."), tr("Are you sure you want to stop the process?"), QMessageBox::Yes|QMessageBox::No, QMessageBox::No); + + if(button != QMessageBox::Yes) + { + return; + } + } + + ULOGGER_DEBUG(""); + if(_camera) + { + UEventsManager::removeHandler(_camera); + //_camera->killSafely(); + + delete _camera; + _camera = 0; + emit stateChanged(kIdle); + } + + //Copy ShowLogs.m from appDirPath/../share/rtabmap/ShowLogs.m to working directory. (appDirPath is in bin) + QString showLogFileSrc = (QApplication::applicationDirPath()+"/../")+SHARE_SHOW_LOG_FILE; + QString showLogFileTarget = (_preferencesDialog->getWorkingDirectory()+"/")+UFile::getName(SHARE_SHOW_LOG_FILE).c_str(); + if(!QFile::exists(showLogFileTarget) && QFile::exists(showLogFileSrc)) + { + QFile::copy(showLogFileSrc, showLogFileTarget); + } + + // copy importfile.m + QString importFileSrc = (QApplication::applicationDirPath()+"/../")+SHARE_IMPORT_FILE; + QString importFileTarget = (_preferencesDialog->getWorkingDirectory()+"/")+UFile::getName(SHARE_IMPORT_FILE).c_str(); + if(!QFile::exists(importFileTarget) && QFile::exists(importFileSrc)) + { + QFile::copy(importFileSrc, importFileTarget); + } +} + +void MainWindow::generateMap() +{ + if(_graphSavingFileName.isEmpty()) + { + _graphSavingFileName = _preferencesDialog->getWorkingDirectory() + "/Graph.dot"; + } + QString path = QFileDialog::getSaveFileName(this, tr("Save File"), _graphSavingFileName, tr("Graphiz file (*.dot)")); + if(!path.isEmpty()) + { + _graphSavingFileName = path; + RtabmapEventCmd * event = new RtabmapEventCmd(RtabmapEventCmd::kCmdGenerateGraph); + event->setStr(path.toStdString()); + this->post(event); // The event is automatically deleted by the EventsManager... + } +} + +void MainWindow::deleteMemory() +{ + QMessageBox::StandardButton button; + QString dbPath = _preferencesDialog->getWorkingDirectory() + "/LTM.db"; + if(_state == kMonitoring) + { + button = QMessageBox::question(this, + tr("Deleting memory..."), + tr("The remote database file \"%1\" will be deleted. Are you sure you want to continue? (This cannot be reverted)").arg(dbPath), + QMessageBox::Yes|QMessageBox::No, + QMessageBox::No); + } + else + { + button = QMessageBox::question(this, + tr("Deleting memory..."), + tr("The database file \"%1\" (%2 MB) will be deleted. Are you sure you want to continue? (This cannot be reverted)").arg(dbPath).arg(UFile::length(dbPath.toStdString())/1000000), + QMessageBox::Yes|QMessageBox::No, + QMessageBox::No); + } + + if(button != QMessageBox::Yes) + { + return; + } + + this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdDeleteMemory)); + this->clearTheCache(); +} + +void MainWindow::openWorkingDirectory() +{ + QString filePath = _preferencesDialog->getWorkingDirectory(); +#if defined(Q_WS_MAC) + QStringList args; + args << "-e"; + args << "tell application \"Finder\""; + args << "-e"; + args << "activate"; + args << "-e"; + args << "select POSIX file \""+filePath+"\""; + args << "-e"; + args << "end tell"; + QProcess::startDetached("osascript", args); +#elif defined(Q_WS_WIN) + QStringList args; + args << "/select," << QDir::toNativeSeparators(filePath); + QProcess::startDetached("explorer", args); +#else + UERROR("Only works on Mac and Windows"); +#endif +} + +void MainWindow::updateEditMenu() +{ + // Update Memory delete database size + if(_state != kMonitoring) + { + QString dbPath = _preferencesDialog->getWorkingDirectory() + "/LTM.db"; + _ui->actionDelete_memory->setText(tr("Delete memory (%1 MB)").arg(UFile::length(dbPath.toStdString())/1000000)); + } +} + +void MainWindow::selectImages() +{ + _preferencesDialog->selectSource(PreferencesDialog::kSrcImages); +} + +void MainWindow::selectVideo() +{ + _preferencesDialog->selectSource(PreferencesDialog::kSrcVideo); +} + +void MainWindow::selectStream() +{ + _preferencesDialog->selectSource(PreferencesDialog::kSrcUsbDevice); +} + +void MainWindow::selectDatabase() +{ + _preferencesDialog->selectSource(PreferencesDialog::kSrcDatabase); +} + +void MainWindow::resetTheMemory() +{ + this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdResetMemory)); + _lastId = 0; + _lastIds.clear(); +} + +void MainWindow::dumpTheMemory() +{ + this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdDumpMemory)); +} + +void MainWindow::dumpThePrediction() +{ + this->post(new RtabmapEventCmd(RtabmapEventCmd::kCmdDumpPrediction)); +} + +void MainWindow::clearTheCache() +{ + _imagesMap.clear(); + _likelihoodCurve->clear(); + _posteriorCurve->clear(); + _lastId = 0; + _lastIds.clear(); + _ui->label_stats_loopClosuresDetected->setText("0"); + _ui->label_stats_loopClosuresReactivatedDetected->setText("0"); + _ui->label_stats_loopClosuresRejected->setText("0"); +} + +void MainWindow::updateElapsedTime() +{ + if(_state == kDetecting || _state == kMonitoring) + { + QString format = "hh:mm:ss"; + _ui->label_elapsedTime->setText((QTime().fromString(_ui->label_elapsedTime->text(), format).addMSecs(_elapsedTime->restart())).toString(format)); + } +} + +void MainWindow::saveFigures() +{ + QList curvesPerFigure; + QStringList curveNames; + _ui->statsToolBox->getFiguresSetup(curvesPerFigure, curveNames); + + if(curvesPerFigure.size() == 0) + { + QMessageBox msgBox; + msgBox.setText("There is no figure shown."); + msgBox.setInformativeText("Do you want to save anyway ? (this will erase the previous saved configuration)"); + msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Cancel); + int ret = msgBox.exec(); + if(ret == QMessageBox::Cancel) + { + return; + } + } + + QStringList curvesPerFigureStr; + for(int i=0; isaveCustomConfig("Figures", "counts", curvesPerFigureStr.join(" ")); + _preferencesDialog->saveCustomConfig("Figures", "curves", curveNames.join(" ")); +} + +void MainWindow::loadFigures() +{ + QString curvesPerFigure = _preferencesDialog->loadCustomConfig("Figures", "counts"); + QString curveNames = _preferencesDialog->loadCustomConfig("Figures", "curves"); + + QStringList curvesPerFigureList = curvesPerFigure.split(" "); + QStringList curvesNamesList = curveNames.split(" "); + + int j=0; + for(int i=0; istatsToolBox->addCurve(curvesNamesList[j++].replace('_', ' ')); + for(int k=1; kstatsToolBox->addCurve(curvesNamesList[j++].replace('_', ' '), false); + } + } + } + +} + +// STATES +// Must be called by the GUI thread, use signal StateChanged() +void MainWindow::changeState(MainWindow::State newState) +{ + // TODO : To protect with mutex ? + switch (newState) + { + case kIdle: + _ui->actionStart->setEnabled(true); + _ui->actionPause->setEnabled(false); + _ui->actionPause->setChecked(false); + _ui->actionPause->setToolTip(tr("Pause")); + _ui->actionStop->setEnabled(false); + _ui->actionPause_on_match->setEnabled(true); + _ui->actionPause_when_a_loop_hypothesis_is_rejected->setEnabled(true); + _ui->actionReset_the_memory->setEnabled(true); + _ui->actionDump_the_memory->setEnabled(true); + _ui->actionDelete_memory->setEnabled(true); + _ui->actionGenerate_map->setEnabled(true); + _ui->actionOpen_working_directory->setEnabled(true); + _ui->actionApply_settings_to_the_detector->setEnabled(true); + _ui->menuSelect_source->setEnabled(true); + _ui->doubleSpinBox_stats_imgRate->setEnabled(true); + _ui->statusbar->clearMessage(); + _state = newState; + _oneSecondTimer->stop(); + break; + + case kStartingDetection: + _ui->actionStart->setEnabled(false); + break; + + case kDetecting: + _ui->actionStart->setEnabled(false); + _ui->actionPause->setEnabled(true); + _ui->actionPause->setChecked(false); + _ui->actionPause->setToolTip(tr("Pause")); + _ui->actionStop->setEnabled(true); + _ui->actionPause_on_match->setEnabled(true); + _ui->actionPause_when_a_loop_hypothesis_is_rejected->setEnabled(true); + _ui->actionReset_the_memory->setEnabled(false); + _ui->actionDump_the_memory->setEnabled(false); + _ui->actionDelete_memory->setEnabled(false); + _ui->actionGenerate_map->setEnabled(false); + _ui->actionOpen_working_directory->setEnabled(true); + _ui->actionApply_settings_to_the_detector->setEnabled(false); + _ui->menuSelect_source->setEnabled(false); + _ui->doubleSpinBox_stats_imgRate->setEnabled(true); + _ui->statusbar->showMessage(tr("Detecting...")); + _state = newState; + _ui->label_elapsedTime->setText("00:00:00"); + _elapsedTime->start(); + _oneSecondTimer->start(); + break; + + case kPaused: + if(_state == kPaused) + { + _ui->actionPause->setToolTip(tr("Pause")); + _ui->actionPause->setChecked(false); + _ui->statusbar->showMessage(tr("Detecting...")); + _ui->actionReset_the_memory->setEnabled(false); + _ui->actionDump_the_memory->setEnabled(false); + _ui->actionDelete_memory->setEnabled(false); + _ui->actionGenerate_map->setEnabled(false); + _state = kDetecting; + _elapsedTime->start(); + _oneSecondTimer->start(); + } + else if(_state == kDetecting) + { + _ui->actionPause->setToolTip(tr("Continue")); + _ui->actionPause->setChecked(true); + _ui->statusbar->showMessage(tr("Paused...")); + _ui->actionReset_the_memory->setEnabled(true); + _ui->actionDump_the_memory->setEnabled(true); + _ui->actionDelete_memory->setEnabled(true); + _ui->actionGenerate_map->setEnabled(true); + _state = kPaused; + _oneSecondTimer->stop(); + } + break; + case kMonitoring: + _ui->actionStart->setEnabled(false); + _ui->actionPause->setEnabled(false); + _ui->actionPause->setChecked(false); + _ui->actionPause->setToolTip(tr("Pause")); + _ui->actionStop->setEnabled(false); + _ui->actionPause_on_match->setEnabled(false); + _ui->actionPause_when_a_loop_hypothesis_is_rejected->setEnabled(false); + _ui->actionReset_the_memory->setEnabled(true); + _ui->actionDump_the_memory->setEnabled(true); + _ui->actionDelete_memory->setEnabled(true); + _ui->actionGenerate_map->setEnabled(false); + _ui->actionOpen_working_directory->setEnabled(false); + _ui->actionApply_settings_to_the_detector->setEnabled(false); + _ui->menuSelect_source->setEnabled(false); + _ui->doubleSpinBox_stats_imgRate->setEnabled(false); + _ui->statusbar->showMessage(tr("Monitoring...")); + _state = newState; + _ui->label_elapsedTime->setText("00:00:00"); + _elapsedTime->start(); + _oneSecondTimer->start(); + break; + default: + break; + } + +} + +} diff --git a/guilib/src/PdfPlot.cpp b/guilib/src/PdfPlot.cpp new file mode 100644 index 0000000000..771063e391 --- /dev/null +++ b/guilib/src/PdfPlot.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "PdfPlot.h" +#include "utilite/ULogger.h" + +namespace rtabmap { + +PdfPlotItem::PdfPlotItem(float dataX, float dataY, float width, int childCount) : + PlotItem(dataX, dataY, width), + _img(0), + _imagesRef(0) +{ + setLikelihood(dataX, dataY, childCount); + _text = new QGraphicsTextItem(this); + _text->setVisible(false); +} + +PdfPlotItem::~PdfPlotItem() +{ + +} + +void PdfPlotItem::setLikelihood(int id, float value, int childCount) +{ + if(id != this->data().x()) + { + delete _img; + _img = 0; + } + this->setData(QPointF(id, value)); + _childCount = childCount; +} + +void PdfPlotItem::showDescription(bool shown) +{ + if(shown) + { + if(!_img && _imagesRef) + { + QImage img; + QMap::const_iterator iter = _imagesRef->find(int(this->data().x())); + if(iter != _imagesRef->constEnd()) + { + if(img.loadFromData(iter.value(), "JPEG")) + { + QPixmap scaled = QPixmap::fromImage(img).scaledToWidth(128); + _img = new QGraphicsPixmapItem(scaled, this); + _img->setVisible(false); + } + } + } + + if(_img) + _text->setPos(this->mapFromScene(4+150,0)); + else + _text->setPos(this->mapFromScene(4,0)); + _text->setPlainText(QString("ID = %1\nValue = %2\nWeight = %3").arg(this->data().x()).arg(this->data().y()).arg(_childCount)); + _text->setVisible(true); + if(_img) + { + _img->setPos(this->mapFromScene(4,0)); + _img->setVisible(true); + } + } + else + { + _text->setVisible(false); + if(_img) + _img->setVisible(false); + } + PlotItem::showDescription(shown); +} + + + + + +PdfPlotCurve::PdfPlotCurve(const QString & name, const QMap * imagesMapRef = 0, QObject * parent) : + PlotCurve(name, parent), + _imagesMapRef(imagesMapRef) +{ + +} + +PdfPlotCurve::~PdfPlotCurve() +{ + +} + +void PdfPlotCurve::clear() +{ + PlotCurve::clear(); +} + +void PdfPlotCurve::setData(const QMap & dataMap, const QMap & weightsMap, int lastId) +{ + ULOGGER_DEBUG("dataSize=%d", dataMap.size()); + if(dataMap.size() > 0) + { + //match the size of the current data + int margin = int((_items.size()+1)/2) - dataMap.size(); + UDEBUG("margin=%d", margin); + while(margin < 0) + { + PdfPlotItem * newItem = new PdfPlotItem(0, 0, -1); + newItem->setImagesRef(_imagesMapRef); + this->_addValue(newItem); + ++margin; + } + while(margin > 0) + { + this->removeItem(0); + --margin; + } + + ULOGGER_DEBUG("itemsize=%d", _items.size()); + + // update values + int index = 0; + for(QMap::const_iterator i=dataMap.begin(); i!=dataMap.end(); ++i, index+=2) + { + ((PdfPlotItem*)_items[index])->setLikelihood(i.key(), i.value(), weightsMap.value(i.key(),-1)); + } + + //reset minMax, this will force the plot to update the axes + this->updateMinMax(); + emit dataChanged(this); + } +} + +} diff --git a/guilib/src/PdfPlot.h b/guilib/src/PdfPlot.h new file mode 100644 index 0000000000..49eceafba4 --- /dev/null +++ b/guilib/src/PdfPlot.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef PDFPLOT_H_ +#define PDFPLOT_H_ + +#include "Plot.h" + +namespace rtabmap { + +class PdfPlotItem : public PlotItem +{ +public: + PdfPlotItem(float dataX, float dataY, float width, int childCount = -1); + virtual ~PdfPlotItem(); + + void setLikelihood(int id, float value, int childCount); + void setImagesRef(const QMap * imagesRef) {_imagesRef = imagesRef;} + + float value() const {return this->data().y();} + int id() const {return this->data().x();} + +protected: + virtual void showDescription(bool shown); + +private: + QGraphicsTextItem * _text; + QGraphicsPixmapItem * _img; + int _childCount; + const QMap * _imagesRef; + +}; + +class PdfPlotCurve : public PlotCurve +{ + Q_OBJECT + +public: + PdfPlotCurve(const QString & name, const QMap * imagesMapRef, QObject * parent = 0); + virtual ~PdfPlotCurve(); + + virtual void clear(); + void setData(const QMap & dataMap, const QMap & weightsMap, int lastId); + +private: + const QMap * _imagesMapRef; +}; + +} + +#endif /* PDFPLOT_H_ */ diff --git a/guilib/src/Plot.cpp b/guilib/src/Plot.cpp new file mode 100644 index 0000000000..4323868b7b --- /dev/null +++ b/guilib/src/Plot.cpp @@ -0,0 +1,2207 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "Plot.h" + +#ifndef PLOT_WIDGET_OUT_OF_LIB +#include "utilite/ULogger.h" +#else +#define ULOGGER_DEBUG(...) +#define ULOGGER_INFO(...) +#define ULOGGER_WARN(...) printf(__VA_ARGS__);printf("\n") +#define ULOGGER_ERROR(...) printf(__VA_ARGS__);printf("\n") +#define UDEBUG(...) +#define UINFO(...) +#define UWARN(...) printf(__VA_ARGS__);printf("\n") +#define UERROR(...) printf(__VA_ARGS__);printf("\n") +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef QT_SVG_LIB +#include +#endif +#include + +#ifndef PLOT_WIDGET_OUT_OF_LIB +namespace rtabmap { +#endif + +PlotItem::PlotItem(qreal dataX, qreal dataY, qreal width) : + QGraphicsEllipseItem(0, 0, width, width, 0), + _previousItem(0), + _nextItem(0) +{ + _data.setX(dataX); + _data.setY(dataY); + this->setZValue(1); + this->setAcceptsHoverEvents(true); + _text = new QGraphicsTextItem(this); + _text->setPlainText(QString("(%1,%2)").arg(_data.x()).arg(_data.y())); + _text->setVisible(false); + this->setFlag(QGraphicsItem::ItemIsFocusable, true); +} + +PlotItem::PlotItem(const QPointF & data, qreal width) : + QGraphicsEllipseItem(0, 0, width, width, 0), + _data(data), + _previousItem(0), + _nextItem(0) +{ + this->setZValue(1); + this->setAcceptsHoverEvents(true); + _text = new QGraphicsTextItem(this); + _text->setPlainText(QString("(%1,%2)").arg(_data.x()).arg(_data.y())); + _text->setVisible(false); + this->setFlag(QGraphicsItem::ItemIsFocusable, true); +} + +PlotItem::~PlotItem() +{ + if(_previousItem && _nextItem) + { + _previousItem->setNextItem(_nextItem); + _nextItem->setPreviousItem(_previousItem); + } + else if(_previousItem) + { + _previousItem->setNextItem(0); + } + else if(_nextItem) + { + _nextItem->setPreviousItem(0); + } +} + +void PlotItem::setData(const QPointF & data) +{ + _data = data; + _text->setPlainText(QString("(%1,%2)").arg(_data.x()).arg(_data.y())); +} + +void PlotItem::setNextItem(PlotItem * nextItem) +{ + if(_nextItem != nextItem) + { + _nextItem = nextItem; + if(nextItem) + { + nextItem->setPreviousItem(this); + } + } +} + +void PlotItem::setPreviousItem(PlotItem * previousItem) +{ + if(_previousItem != previousItem) + { + _previousItem = previousItem; + if(previousItem) + { + previousItem->setNextItem(this); + } + } +} + +void PlotItem::showDescription(bool shown) +{ + ULOGGER_DEBUG(""); + if(shown) + { + this->setPen(QPen(Qt::black, 2)); + if(this->scene()) + { + QRectF rect = this->scene()->sceneRect(); + QPointF p = this->pos(); + QRectF br = _text->boundingRect(); + + // Make sure the text is always in the scene + if(p.x() - br.width() < 0) + { + p.setX(0); + } + else if(p.x() + br.width() > rect.width()) + { + p.setX(rect.width() - br.width()); + } + else + { + p.setX(p.x() - br.width()); + } + + if(p.y() - br.height() < 0) + { + p.setY(0); + } + else + { + p.setY(p.y() - br.height()); + } + + _text->setPos(this->mapFromScene(p)); + } + + _text->setVisible(true); + } + else + { + this->setPen(QPen(Qt::black, 1)); + _text->setVisible(false); + } +} + +void PlotItem::hoverEnterEvent(QGraphicsSceneHoverEvent * event) +{ + QGraphicsScene * scene = this->scene(); + if(scene && scene->focusItem() == 0) + { + this->showDescription(true); + } + else + { + this->setPen(QPen(Qt::black, 2)); + } + QGraphicsEllipseItem::hoverEnterEvent(event); +} + +void PlotItem::hoverLeaveEvent(QGraphicsSceneHoverEvent * event) +{ + if(!this->hasFocus()) + { + this->showDescription(false); + } + QGraphicsEllipseItem::hoverEnterEvent(event); +} + +void PlotItem::focusInEvent(QFocusEvent * event) +{ + this->showDescription(true); + QGraphicsEllipseItem::focusInEvent(event); +} + +void PlotItem::focusOutEvent(QFocusEvent * event) +{ + this->showDescription(false); + QGraphicsEllipseItem::focusOutEvent(event); +} + +void PlotItem::keyReleaseEvent(QKeyEvent * keyEvent) +{ + //Get the next/previous visible item + if(keyEvent->key() == Qt::Key_Right) + { + PlotItem * next = _nextItem; + while(next && !next->isVisible()) + { + next = next->nextItem(); + } + if(next && next->isVisible()) + { + this->clearFocus(); + next->setFocus(); + } + } + else if(keyEvent->key() == Qt::Key_Left) + { + PlotItem * previous = _previousItem; + while(previous && !previous->isVisible()) + { + previous = previous->previousItem(); + } + if(previous && previous->isVisible()) + { + this->clearFocus(); + previous->setFocus(); + } + } + QGraphicsEllipseItem::keyReleaseEvent(keyEvent); +} + + + + + +PlotCurve::PlotCurve(const QString & name, QObject * parent) : + QObject(parent), + _plot(0), + _name(name), + _defaultStepX(1), + _startX(0), + _visible(true), + _valuesShown(false) +{ +} + +PlotCurve::PlotCurve(const QString & name, QVector data, QObject * parent) : + QObject(parent), + _plot(0), + _name(name), + _defaultStepX(1), + _startX(0), + _visible(true), + _valuesShown(false) +{ + this->setData(data); +} + +PlotCurve::PlotCurve(const QString & name, const QVector & x, const QVector & y, QObject * parent) : + QObject(parent), + _plot(0), + _name(name), + _defaultStepX(1), + _startX(0), + _visible(true), + _valuesShown(false) +{ + this->setData(x, y); +} + +PlotCurve::~PlotCurve() +{ + if(_plot) + { + _plot->removeCurve(this); + } + ULOGGER_DEBUG("%s", this->name().toStdString().c_str()); + this->clear(); +} + +void PlotCurve::attach(Plot * plot) +{ + if(!plot || plot == _plot) + { + return; + } + this->setParent(plot); + if(_plot) + { + _plot->removeCurve(this); + } + _plot = plot; + for(int i=0; i<_items.size(); ++i) + { + _plot->scene()->addItem(_items.at(i)); + } +} + +void PlotCurve::detach(Plot * plot) +{ + ULOGGER_DEBUG("curve=\"%s\" from plot=\"%s\"", this->objectName().toStdString().c_str(), plot?plot->objectName().toStdString().c_str():""); + if(plot && _plot == plot) + { + _plot = 0; + for(int i=0; i<_items.size(); ++i) + { + if(_items.at(i)->scene()) + { + _items.at(i)->scene()->removeItem(_items.at(i)); + } + } + } +} + +void PlotCurve::updateMinMax() +{ + float x,y; + const PlotItem * item; + if(!_items.size()) + { + _minMax = QVector(); + } + else + { + _minMax = QVector(4); + } + for(int i=0; i<_items.size(); ++i) + { + item = qgraphicsitem_cast(_items.at(i)); + if(item) + { + x = item->data().x(); + y = item->data().y(); + if(i==1) + { + _minMax[0] = x; + _minMax[1] = x; + _minMax[2] = y; + _minMax[3] = y; + } + else + { + if(x<_minMax[0]) _minMax[0] = x; + if(x>_minMax[1]) _minMax[1] = x; + if(y<_minMax[2]) _minMax[2] = y; + if(y>_minMax[3]) _minMax[3] = y; + } + } + } +} + +void PlotCurve::_addValue(PlotItem * data) +{ + // add item + if(data) + { + float x = data->data().x(); + float y = data->data().y(); + if(_minMax.size() != 4) + { + _minMax = QVector(4); + } + if(_items.size()) + { + data->setPreviousItem((PlotItem *)_items.last()); + + //apply scale + QGraphicsLineItem * line = new QGraphicsLineItem(); + line->setPen(_pen); + line->setVisible(false); + _items.append(line); + if(_plot) + { + _plot->scene()->addItem(line); + } + + //Update min/max + if(x<_minMax[0]) _minMax[0] = x; + if(x>_minMax[1]) _minMax[1] = x; + if(y<_minMax[2]) _minMax[2] = y; + if(y>_minMax[3]) _minMax[3] = y; + } + else + { + _minMax[0] = x; + _minMax[1] = x; + _minMax[2] = y; + _minMax[3] = y; + } + _items.append(data); + data->setVisible(false); + //data->showDescription(_valuesShown); + if(_plot) + { + _plot->scene()->addItem(_items.last()); + } + } + else + { + ULOGGER_ERROR("Data is null ?!?"); + } +} + +void PlotCurve::addValue(PlotItem * data) +{ + // add item + if(data) + { + this->_addValue(data); + emit dataChanged(this); + } +} + +void PlotCurve::addValue(float x, float y) +{ + float width = 2; // TODO warn : hard coded value! + this->addValue(new PlotItem(x,y,width)); +} + +void PlotCurve::addValue(float y) +{ + float x = 0; + if(_items.size()) + { + PlotItem * lastItem = (PlotItem *)_items.last(); + x = lastItem->data().x() + _defaultStepX; + } + else + { + x = _startX; + } + this->addValue(x,y); +} + +void PlotCurve::addValue(const QString & value) +{ + bool ok; + float v = value.toFloat(&ok); + if(ok) + { + this->addValue(v); + } + else + { + ULOGGER_ERROR("Value not valid, must be a number, received %s", value.toStdString().c_str()); + } +} + +void PlotCurve::addValues(QVector & data) +{ + for(int i=0; i_addValue(data.at(i)); + } + emit dataChanged(this); +} + +void PlotCurve::addValues(const QVector & xs, const QVector & ys) +{ + float width = 2; // TODO warn : hard coded value! + for(int i=0; i_addValue(new PlotItem(xs.at(i),ys.at(i),width)); + } + emit dataChanged(this); +} + +void PlotCurve::addValues(const QVector & ys) +{ + float x = 0; + float width = 2; // TODO warn : hard coded value! + for(int i=0; idata().x() + _defaultStepX; + } + else + { + x = _startX; + } + this->_addValue(new PlotItem(x,ys.at(i),width)); + } + emit dataChanged(this); +} + +int PlotCurve::removeItem(int index) +{ + if(index >= 0 && index < _items.size()) + { + if(index!=0) + { + index-=1; + delete _items.takeAt(index); // the line + } + else if(_items.size()>1) + { + delete _items.takeAt(index+1); // the line + } + PlotItem * item = (PlotItem *)_items.takeAt(index); // the plot item + //Update min/max + if(_minMax.size() == 4) + { + if(item->data().x() == _minMax[0] || item->data().x() == _minMax[1] || + item->data().y() == _minMax[2] || item->data().y() == _minMax[3]) + { + if(_items.size()) + { + PlotItem * tmp = (PlotItem *)_items.at(0); + float x = tmp->data().x(); + float y = tmp->data().y(); + _minMax[0]=x; + _minMax[1]=x; + _minMax[2]=y; + _minMax[3]=y; + for(int i = 2; i<_items.size(); i+=2) + { + tmp = (PlotItem*)_items.at(i); + x = tmp->data().x(); + y = tmp->data().y(); + if(x<_minMax[0]) _minMax[0] = x; + if(x>_minMax[1]) _minMax[1] = x; + if(y<_minMax[2]) _minMax[2] = y; + if(y>_minMax[3]) _minMax[3] = y; + } + } + else + { + _minMax = QVector(); + } + } + } + delete item; + } + + return index; +} + +void PlotCurve::removeItem(PlotItem * item) // ownership is transfered to the caller +{ + for(int i=0; i<_items.size(); ++i) + { + if(_items.at(i) == item) + { + if(i!=0) + { + i-=1; + delete _items[i]; + _items.removeAt(i); + } + else if(_items.size()>1) + { + delete _items[i+1]; + _items.removeAt(i+1); + } + item->scene()->removeItem(item); + _items.removeAt(i); + break; + } + } +} + +void PlotCurve::clear() +{ + ULOGGER_DEBUG("%s", this->name().toStdString().c_str()); + qDeleteAll(_items); + _items.clear(); +} + +void PlotCurve::setPen(const QPen & pen) +{ + _pen = pen; + for(int i=1; i<_items.size(); i+=2) + { + ((QGraphicsLineItem*) _items.at(i))->setPen(_pen); + } +} + +void PlotCurve::setBrush(const QBrush & brush) +{ + _brush = brush; + ULOGGER_WARN("Not used..."); +} + +void PlotCurve::update(float scaleX, float scaleY, float offsetX, float offsetY, int xDir, int yDir, bool allDataKept) +{ + //ULOGGER_DEBUG("scaleX=%f, scaleY=%f, offsetX=%f, offsetY=%f, xDir=%d, yDir=%d, _plot->scene()->width()=%f, _plot->scene()->height=%f", scaleX, scaleY, offsetX, offsetY, xDir, yDir,_plot->scene()->width(),_plot->scene()->height()); + //make sure direction values are 1 or -1 + xDir<0?xDir=-1:xDir=1; + yDir<0?yDir=-1:yDir=1; + + bool hide = false; + for(int i=_items.size()-1; i>=0; --i) + { + if(i%2 == 0) + { + PlotItem * item = (PlotItem *)_items.at(i); + if(hide) + { + if(allDataKept) + { + // if not visible, stop looping... all other items are normally already hided + if(!item->isVisible()) + { + break; + } + item->setVisible(false); + } + else + { + //remove the item with his line + i = this->removeItem(i); + } + } + else + { + item->setPos(((xDir*item->data().x()+offsetX)*scaleX-item->rect().width()/2), + ((yDir*item->data().y()+offsetY)*scaleY-item->rect().width()/2)); + if(!item->isVisible()) + { + item->setVisible(true); + } + } + + } + else + { + if(hide) + { + _items.at(i)->setVisible(false); + } + else + { + PlotItem * from = (PlotItem *)_items.at(i-1); + PlotItem * to = (PlotItem *)_items.at(i+1); + QGraphicsLineItem * lineItem = (QGraphicsLineItem *)_items.at(i); + lineItem->setLine((xDir*from->data().x()+offsetX)*scaleX, + (yDir*from->data().y()+offsetY)*scaleY, + (xDir*to->data().x()+offsetX)*scaleX, + (yDir*to->data().y()+offsetY)*scaleY); + if(!lineItem->isVisible()) + { + lineItem->setVisible(true); + } + //Don't update not visible items + // (Detect also if the curve goes forward or backward) + QLineF line = lineItem->line(); + if((line.x1() <= line.x2() && line.x2() < 0-((line.x2() - line.x1()))) || + (line.x1() > line.x2() && line.x2() > lineItem->scene()->sceneRect().width() + ((line.x1() - line.x2())))) + { + hide = true; + } + + } + } + } + +} + +int PlotCurve::itemsSize() +{ + return _items.size(); +} + +QPointF PlotCurve::getItemData(int index) +{ + QPointF data; + //make sure the index point to a PlotItem {PlotItem, line, PlotItem, line...} + if(index>=0 && index < _items.size() && index % 2 == 0 ) + { + data = ((PlotItem*)_items.at(index))->data(); + } + else + { + ULOGGER_ERROR("Wrong index, not pointing on a PlotItem"); + } + return data; +} + +void PlotCurve::setVisible(bool visible) +{ + _visible = visible; + for(int i=0; i<_items.size(); ++i) + { + _items.at(i)->setVisible(visible); + } +} + +void PlotCurve::setData(QVector & data) +{ + this->clear(); + for(int i = 0; iaddValue(data[i]); + } +} + +void PlotCurve::setData(const QVector & x, const QVector & y) +{ + if(x.size() == y.size()) + { + this->clear(); + for(int i = 0; iaddValue(x[i], y[i]); + } + } + else + { + ULOGGER_ERROR("Data vectors have not the same size."); + } +} + +void PlotCurve::getData(QVector & x, QVector & y) const +{ + x.clear(); + y.clear(); + if(_items.size()) + { + x.resize((_items.size()-1)/2+1); + y.resize(x.size()); + int j=0; + for(int i=0; i<_items.size(); i+=2) + { + x[j] = ((PlotItem*)_items.at(i))->data().x(); + y[j++] = ((PlotItem*)_items.at(i))->data().y(); + } + } +} + + + + + +ThresholdCurve::ThresholdCurve(const QString & name, float thesholdValue, Qt::Orientation orientation, QObject * parent) : + PlotCurve(name, parent), + _orientation(orientation) +{ + if(_orientation == Qt::Horizontal) + { + this->addValue(0, thesholdValue); + this->addValue(1, thesholdValue); + } + else + { + this->addValue(thesholdValue, 0); + this->addValue(thesholdValue, 1); + } +} + +ThresholdCurve::~ThresholdCurve() +{ + +} + +void ThresholdCurve::setThreshold(float threshold) +{ + ULOGGER_DEBUG("%f", threshold); + if(_items.size() == 3) + { + PlotItem * item = 0; + if(_orientation == Qt::Horizontal) + { + item = (PlotItem*)_items.at(0); + item->setData(QPointF(item->data().x(), threshold)); + item = (PlotItem*)_items.at(2); + item->setData(QPointF(item->data().x(), threshold)); + } + else + { + item = (PlotItem*)_items.at(0); + item->setData(QPointF(threshold, item->data().y())); + item = (PlotItem*)_items.at(2); + item->setData(QPointF(threshold, item->data().y())); + } + } + else + { + ULOGGER_ERROR("A threshold must has only 3 items (1 PlotItem + 1 QGraphicsLineItem + 1 PlotItem)"); + } +} + +void ThresholdCurve::setOrientation(Qt::Orientation orientation) +{ + if(_orientation != orientation) + { + _orientation = orientation; + if(_items.size() == 3) + { + PlotItem * item = 0; + item = (PlotItem*)_items.at(0); + item->setData(QPointF(item->data().y(), item->data().x())); + item = (PlotItem*)_items.at(2); + item->setData(QPointF(item->data().y(), item->data().x())); + } + else + { + ULOGGER_ERROR("A threshold must has only 3 items (1 PlotItem + 1 QGraphicsLineItem + 1 PlotItem)"); + } + } +} + +void ThresholdCurve::update(float scaleX, float scaleY, float offsetX, float offsetY, int xDir, int yDir, bool allDataKept) +{ + if(_items.size() == 3) + { + if(_plot) + { + PlotItem * item = 0; + if(_orientation == Qt::Horizontal) + { + //(xDir*item->data().x()+offsetX)*scaleX + item = (PlotItem*)_items.at(0); + item->setData(QPointF(-offsetX/xDir, item->data().y())); + item = (PlotItem*)_items.at(2); + item->setData(QPointF( (_plot->scene()->width()/scaleX-offsetX)/xDir, item->data().y())); + } + else + { + item = (PlotItem*)_items.at(0); + item->setData(QPointF(item->data().x(), -offsetY/yDir)); + item = (PlotItem*)_items.at(2); + item->setData(QPointF(item->data().x(), (_plot->scene()->height()/scaleY-offsetY)/yDir)); + } + this->updateMinMax(); + } + } + else + { + ULOGGER_ERROR("A threshold must has only 3 items (1 PlotItem + 1 QGraphicsLineItem + 1 PlotItem)"); + } + PlotCurve::update(scaleX, scaleY, offsetX, offsetY, xDir, yDir, allDataKept); +} + + + + + + + +PlotAxis::PlotAxis(Qt::Orientation orientation, float min, float max, QWidget * parent) : + QWidget(parent), + _orientation(orientation), + _reversed(false), + _gradMaxDigits(4), + _border(0) +{ + if(_orientation == Qt::Vertical) + { + _reversed = true; // default bottom->up + } +#ifdef WIN32 + this->setMinimumSize(15, 25); +#else + this->setMinimumSize(15, 25); +#endif + this->setAxis(min, max); // this initialize all attributes +} + +PlotAxis::~PlotAxis() +{ + ULOGGER_DEBUG(""); +} + +// Vertical :bottom->up, horizontal :right->left +void PlotAxis::setReversed(bool reversed) +{ + if(_reversed != reversed) + { + float min = _min; + _min = _max; + _max = min; + } + _reversed = reversed; +} + +void PlotAxis::setAxis(float & min, float & max) +{ + int borderMin = 0; + int borderMax = 0; + if(_orientation == Qt::Vertical) + { + borderMin = borderMax = this->fontMetrics().height()/2; + } + else + { + borderMin = this->fontMetrics().width(QString::number(_min,'g',_gradMaxDigits))/2; + borderMax = this->fontMetrics().width(QString::number(_max,'g',_gradMaxDigits))/2; + } + int border = borderMin>borderMax?borderMin:borderMax; + int borderDelta; + int length; + if(_orientation == Qt::Vertical) + { + length = (this->height()-border*2); + } + else + { + length = (this->width()-border*2); + } + + if(length <= 70) + { + _count = 5; + } + else if(length <= 175) + { + _count = 10; + } + else if(length <= 350) + { + _count = 20; + } + else if(length <= 700) + { + _count = 40; + } + else if(length <= 1000) + { + _count = 60; + } + else if(length <= 1300) + { + _count = 80; + } + else + { + _count = 100; + } + + // Rounding min and max + if(min != max) + { + float mul = 1; + float rangef = fabsf(max - min); + int countStep = _count/5; + float val; + for(int i=0; i<6; ++i) + { + val = (rangef/countStep) * mul; + if( val >= 1 && val < 10) + { + break; + } + else if(val<1) + { + mul *= 10; + } + else + { + mul /= 10; + } + } + //ULOGGER_DEBUG("min=%f, max=%f", min, max); + int minR = min*mul-0.9; + int maxR = max*mul+0.9; + min = float(minR)/mul; + max = float(maxR)/mul; + //ULOGGER_DEBUG("mul=%f, minR=%d, maxR=%d,countStep=%d", mul, minR, maxR, countStep); + } + + _min = min; + _max = max; + + if(_reversed) + { + _min = _max; + _max = min; + } + + if(_orientation == Qt::Vertical) + { + _step = length/_count; + borderDelta = length - (_step*_count); + } + else + { + _step = length/_count; + borderDelta = length - (_step*_count); + } + + if(borderDelta%2 != 0) + { + borderDelta+=1; + } + + _border = border + borderDelta/2; + + //Resize estimation + if(_orientation == Qt::Vertical) + { + int minWidth = 0; + for (int i = 0; i <= _count; i+=5) + { + QString n(QString::number(_min + (i/5)*((_max-_min)/(_count/5)),'g',_gradMaxDigits)); + if(this->fontMetrics().width(n) > minWidth) + { + minWidth = this->fontMetrics().width(n); + } + } + this->setMinimumWidth(15+minWidth); + } +} + +void PlotAxis::paintEvent(QPaintEvent * event) +{ + QPainter painter(this); + if(_orientation == Qt::Vertical) + { + painter.translate(0, _border); + for (int i = 0; i <= _count; ++i) + { + if(i%5 == 0) + { + painter.drawLine(this->width(), 0, this->width()-10, 0); + QLabel n(QString::number(_min + (i/5)*((_max-_min)/(_count/5)),'g',_gradMaxDigits)); + painter.drawText(this->width()-(12+n.sizeHint().width()), n.sizeHint().height()/2-2, n.text()); + } + else + { + painter.drawLine(this->width(), 0, this->width()-5, 0); + } + painter.translate(0, _step); + } + } + else + { + painter.translate(_border, 0); + for (int i = 0; i <= _count; ++i) + { + if(i%5 == 0) + { + painter.drawLine(0, 0, 0, 10); + QLabel n(QString::number(_min + (i/5)*((_max-_min)/(_count/5)),'g',_gradMaxDigits)); + painter.drawText(-(n.sizeHint().width()/2)+1, 22, n.text()); + } + else + { + painter.drawLine(0, 0, 0, 5); + } + painter.translate(_step, 0); + } + } +} + + + + +PlotLegendItem::PlotLegendItem(const PlotCurve * curve, QWidget * parent) : + QPushButton(parent), + _curve(curve) +{ + QString nameSpaced = curve->name(); + nameSpaced.replace('_', ' '); + this->setText(nameSpaced); + + _aChangeText = new QAction(tr("Change text..."), this); + _aResetText = new QAction(tr("Reset text..."), this); + _aRemoveCurve = new QAction(tr("Remove this curve"), this); + _aCopyToClipboard = new QAction(tr("Copy curve data to the clipboard"), this); + _menu = new QMenu(tr("Curve"), this); + _menu->addAction(_aChangeText); + _menu->addAction(_aResetText); + _menu->addAction(_aRemoveCurve); + _menu->addAction(_aCopyToClipboard); +} + +PlotLegendItem::~PlotLegendItem() +{ + +} +void PlotLegendItem::contextMenuEvent(QContextMenuEvent * event) +{ + QAction * action = _menu->exec(event->globalPos()); + if(action == _aChangeText) + { + bool ok; + QString text = QInputDialog::getText(this, _aChangeText->text(), tr("Name :"), QLineEdit::Normal, this->text(), &ok); + if(ok && !text.isEmpty()) + { + this->setText(text); + } + } + else if(action == _aResetText) + { + if(_curve) + { + this->setText(_curve->name()); + } + } + else if(action == _aRemoveCurve) + { + emit legendItemRemoved(_curve); + } + else if (action == _aCopyToClipboard) + { + if(_curve) + { + QVector x; + QVector y; + _curve->getData(x, y); + QString textX; + QString textY; + for(int i=0; isetText((textX+"\n")+textY); + } + } +} + + + + + + +PlotLegend::PlotLegend(QWidget * parent) : + QWidget(parent), + _flat(true) +{ + //menu + _aUseFlatButtons = new QAction(tr("Use flat buttons"), this); + _aUseFlatButtons->setCheckable(true); + _aUseFlatButtons->setChecked(_flat); + _menu = new QMenu(tr("Legend"), this); + _menu->addAction(_aUseFlatButtons); + + QVBoxLayout * vLayout = new QVBoxLayout(this); + vLayout->setContentsMargins(0,0,0,0); + this->setLayout(vLayout); + vLayout->addStretch(0); + vLayout->setSpacing(0); +} + +PlotLegend::~PlotLegend() +{ + ULOGGER_DEBUG(""); +} + +void PlotLegend::setFlat(bool on) +{ + if(_flat != on) + { + _flat = on; + QList items = this->findChildren(); + for(int i=0; isetFlat(_flat); + items.at(i)->setChecked(!items.at(i)->isChecked()); + } + _aUseFlatButtons->setChecked(_flat); + } +} + +void PlotLegend::addItem(const PlotCurve * curve) +{ + if(curve) + { + PlotLegendItem * legendItem = new PlotLegendItem(curve, this); + legendItem->setFlat(_flat); + legendItem->setCheckable(true); + legendItem->setChecked(false); + legendItem->setIcon(QIcon(this->createSymbol(curve->pen(), curve->brush()))); + legendItem->setIconSize(QSize(25,20)); + connect(legendItem, SIGNAL(toggled(bool)), this, SLOT(redirectToggled(bool))); + connect(legendItem, SIGNAL(legendItemRemoved(const PlotCurve *)), this, SLOT(removeLegendItem(const PlotCurve *))); + + // layout + QHBoxLayout * hLayout = new QHBoxLayout(); + hLayout->addWidget(legendItem); + hLayout->addStretch(0); + hLayout->setMargin(0); + + // add to the legend + ((QVBoxLayout*)this->layout())->insertLayout(this->layout()->count()-1, hLayout); + } +} + +QPixmap PlotLegend::createSymbol(const QPen & pen, const QBrush & brush) +{ + QPixmap pixmap(50, 50); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + QPen p = pen; + p.setWidthF(4.0); + painter.setPen(p); + painter.drawLine(0.0, 25.0, 50.0, 25.0); + return pixmap; +} + +void PlotLegend::removeLegendItem(const PlotCurve * curve) +{ + QList items = this->findChildren(); + for(int i=0; icurve() == curve) + { + delete items.at(i); + emit legendItemRemoved(curve); + } + } +} + +void PlotLegend::contextMenuEvent(QContextMenuEvent * event) +{ + QAction * action = _menu->exec(event->globalPos()); + if(action == _aUseFlatButtons) + { + this->setFlat(_aUseFlatButtons->isChecked()); + } +} + +void PlotLegend::redirectToggled(bool toggled) +{ + if(sender()) + { + PlotLegendItem * item = qobject_cast(sender()); + if(item) + { + emit legendItemToggled(item->curve(), _flat?!toggled:toggled); + } + } +} + + + + + + + +OrientableLabel::OrientableLabel(const QString & text, Qt::Orientation orientation, QWidget * parent) : + QLabel(text, parent), + _orientation(orientation) +{ +} + +OrientableLabel::~OrientableLabel() +{ +} + +QSize OrientableLabel::sizeHint() const +{ + QSize size = QLabel::sizeHint(); + if (_orientation == Qt::Vertical) + size.transpose(); + return size; + +} + +QSize OrientableLabel::minimumSizeHint() const +{ + QSize size = QLabel::minimumSizeHint(); + if (_orientation == Qt::Vertical) + size.transpose(); + return size; +} + +void OrientableLabel::setOrientation(Qt::Orientation orientation) +{ + _orientation = orientation; + switch(orientation) + { + case Qt::Horizontal: + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + break; + + case Qt::Vertical: + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + break; + } +} + +void OrientableLabel::paintEvent(QPaintEvent* event) +{ + QPainter p(this); + QRect r = rect(); + switch (_orientation) + { + case Qt::Horizontal: + break; + case Qt::Vertical: + p.rotate(-90); + p.translate(-height(), 0); + QSize size = r.size(); + size.transpose(); + r.setSize(size); + break; + } + p.drawText(r, this->alignment() | (this->wordWrap()?Qt::TextWordWrap:0), this->text()); +} + + + + + + + + + + + + + +Plot::Plot(QWidget *parent) : + QWidget(parent), + _maxVisibleItems(-1) +{ + this->setupUi(); + this->createActions(); + this->createMenus(); + + // This will update actions + this->showLegend(true); + this->setMaxVisibleItems(0); + this->showGrid(false); + this->showRefreshRate(false); + this->keepAllData(false); + + for(int i=0; i<4; ++i) + { + _axisMaximums[i] = 0; + _axisMaximumsSet[i] = false; + if(i<2) + { + _fixedAxis[i] = false; + } + } + + _refreshIntervalTime.start(); + _lowestRefreshRate = 99; + _refreshStartTime.start(); + + _penStyleCount = rand() % 10 + 1; // rand 1->10 + _workingDirectory = QDir::homePath(); +} + +Plot::~Plot() +{ + ULOGGER_DEBUG("%s", this->title().toStdString().c_str()); + QList curves = _curves.values(); + for(int i=0; iremoveCurve(curves.at(i)); + } +} + +void Plot::setupUi() +{ + _legend = new PlotLegend(this); + _view = new QGraphicsView(this); + _view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + _view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + _view->setScene(new QGraphicsScene(0,0,0,0,this)); + _verticalAxis = new PlotAxis(Qt::Vertical, 0, 1, this); + _horizontalAxis = new PlotAxis(Qt::Horizontal, 0, 1, this); + _title = new QLabel(""); + _xLabel = new QLabel(""); + _refreshRate = new QLabel(""); + _yLabel = new OrientableLabel(""); + _yLabel->setOrientation(Qt::Vertical); + _title->setAlignment(Qt::AlignCenter); + _xLabel->setAlignment(Qt::AlignCenter); + _yLabel->setAlignment(Qt::AlignCenter); + _refreshRate->setAlignment(Qt::AlignCenter); + _title->setWordWrap(true); + _xLabel->setWordWrap(true); + _yLabel->setWordWrap(true); + _title->setVisible(false); + _xLabel->setVisible(false); + _yLabel->setVisible(false); + _refreshRate->setVisible(false); + + //layouts + QGridLayout * grid = new QGridLayout(); + grid->setContentsMargins(0,0,0,0); + this->setLayout(grid); + grid->addWidget(_title, 0, 2); + grid->addWidget(_yLabel, 1, 0); + grid->addWidget(_verticalAxis, 1, 1); + grid->addWidget(_refreshRate, 2, 1); + grid->addWidget(_view, 1, 2); + grid->setColumnStretch(2, 1); + grid->addWidget(_horizontalAxis, 2, 2); + grid->addWidget(_xLabel, 3, 2); + grid->addWidget(_legend, 1, 3); + connect(_legend, SIGNAL(legendItemToggled(const PlotCurve *, bool)), this, SLOT(showCurve(const PlotCurve *, bool))); + connect(_legend, SIGNAL(legendItemRemoved(const PlotCurve *)), this, SLOT(removeCurve(const PlotCurve *))); +} + +void Plot::createActions() +{ + _aShowLegend = new QAction(tr("Show legend"), this); + _aShowLegend->setCheckable(true); + _aShowGrid = new QAction(tr("Show grid"), this); + _aShowGrid->setCheckable(true); + _aShowRefreshRate = new QAction(tr("Show refresh rate"), this); + _aShowRefreshRate->setCheckable(true); + _aKeepAllData = new QAction(tr("Keep all data"), this); + _aKeepAllData->setCheckable(true); + _aLimit0 = new QAction(tr("No maximum items shown"), this); + _aLimit10 = new QAction(tr("10"), this); + _aLimit50 = new QAction(tr("50"), this); + _aLimit100 = new QAction(tr("100"), this); + _aLimit500 = new QAction(tr("500"), this); + _aLimit1000 = new QAction(tr("1000"), this); + _aLimitCustom = new QAction(tr(""), this); + _aLimit0->setCheckable(true); + _aLimit10->setCheckable(true); + _aLimit50->setCheckable(true); + _aLimit100->setCheckable(true); + _aLimit500->setCheckable(true); + _aLimit1000->setCheckable(true); + _aLimitCustom->setCheckable(true); + _aLimitCustom->setVisible(false); + _aAddVerticalLine = new QAction(tr("Vertical line..."), this); + _aAddHorizontalLine = new QAction(tr("Horizontal line..."), this); + _aChangeTitle = new QAction(tr("Change title"), this); + _aChangeXLabel = new QAction(tr("Change X label..."), this); + _aChangeYLabel = new QAction(tr("Change Y label..."), this); + _aYLabelVertical = new QAction(tr("Vertical orientation"), this); + _aYLabelVertical->setCheckable(true); + _aYLabelVertical->setChecked(true); + _aSaveFigure = new QAction(tr("Save figure..."), this); + _aAutoScreenCapture = new QAction(tr("Auto screen capture"), this); + _aAutoScreenCapture->setCheckable(true); + _aClearData = new QAction(tr("Clear data"), this); + + QActionGroup * grpLimit = new QActionGroup(this); + grpLimit->addAction(_aLimit0); + grpLimit->addAction(_aLimit10); + grpLimit->addAction(_aLimit50); + grpLimit->addAction(_aLimit100); + grpLimit->addAction(_aLimit500); + grpLimit->addAction(_aLimit1000); + grpLimit->addAction(_aLimitCustom); + _aLimit0->setChecked(true); +} + +void Plot::createMenus() +{ + _menu = new QMenu(tr("Plot"), this); + _menu->addAction(_aShowLegend); + _menu->addAction(_aShowGrid); + _menu->addAction(_aShowRefreshRate); + _menu->addAction(_aKeepAllData); + _menu->addSeparator()->setStatusTip(tr("Maximum items shown")); + _menu->addAction(_aLimit0); + _menu->addAction(_aLimit10); + _menu->addAction(_aLimit50); + _menu->addAction(_aLimit100); + _menu->addAction(_aLimit500); + _menu->addAction(_aLimit1000); + _menu->addAction(_aLimitCustom); + _menu->addSeparator(); + QMenu * addLineMenu = _menu->addMenu(tr("Add line")); + addLineMenu->addAction(_aAddHorizontalLine); + addLineMenu->addAction(_aAddVerticalLine); + _menu->addSeparator(); + _menu->addAction(_aChangeTitle); + _menu->addAction(_aChangeXLabel); + QMenu * yLabelMenu = _menu->addMenu(tr("Y label")); + yLabelMenu->addAction(_aChangeYLabel); + yLabelMenu->addAction(_aYLabelVertical); + _menu->addAction(_aSaveFigure); + _menu->addAction(_aAutoScreenCapture); + _menu->addSeparator(); + _menu->addAction(_aClearData); + +} + +PlotCurve * Plot::addCurve(const QString & curveName) +{ + // add curve + PlotCurve * curve = new PlotCurve(curveName, this); + //curve->pen() = QPen((Qt::PenStyle)(_penStyleCount++ % 4 + 2)); + curve->setPen(this->getRandomPenColored()); + this->addCurve(curve); + return curve; +} + +// Ownership is transferred only if true is returned, +// Returning false if a curve with +// the same name is already added to the plot. +bool Plot::addCurve(PlotCurve * curve) +{ + if(curve) + { + // add curve + _curves.insert(curve, curve); + curve->attach(this); // ownership is transferred + this->updateAxis(curve); + curve->setStartX(_axisMaximums[1]); + connect(curve, SIGNAL(dataChanged(const PlotCurve *)), this, SLOT(updateAxis())); + + _legend->addItem(curve); + + ULOGGER_DEBUG("Curve \"%s\" added to plot \"%s\"", curve->name().toStdString().c_str(), this->title().toStdString().c_str()); + + this->update(); + return true; + } + else + { + ULOGGER_ERROR("The curve is null!"); + } + return false; +} + +QStringList Plot::curveNames() +{ + QStringList names; + for(QMap::iterator iter = _curves.begin(); iter!=_curves.end(); ++iter) + { + if(*iter) + { + names.append((*iter)->name()); + } + } + return names; +} + +bool Plot::contains(const QString & curveName) +{ + for(QMap::iterator iter = _curves.begin(); iter!=_curves.end(); ++iter) + { + if(*iter && (*iter)->name().compare(curveName) == 0) + { + return true; + } + } + return false; +} + +QPen Plot::getRandomPenColored() +{ + return QPen((Qt::GlobalColor)(_penStyleCount++ % 12 + 7 )); +} + +void Plot::replot() +{ + QList curves = _curves.values(); + if(_maxVisibleItems>0) + { + PlotCurve * c = 0; + int maxItem = 0; + // find the curve with the most items + for(QList::iterator i=curves.begin(); i!=curves.end(); ++i) + { + if((*i)->isVisible() && ((PlotCurve *)(*i))->itemsSize() > maxItem) + { + c = *i; + maxItem = c->itemsSize(); + } + } + if(c && (maxItem-1)/2+1 > _maxVisibleItems) + { + _axisMaximums[0] = c->getItemData((c->itemsSize()-1) -_maxVisibleItems*2).x(); + } + } + + float axis[4] = {0}; + for(int i=0; i<4; ++i) + { + axis[i] = _axisMaximums[i]; + } + + _verticalAxis->setAxis(axis[2], axis[3]); + _horizontalAxis->setAxis(axis[0], axis[1]); + + //ULOGGER_DEBUG("x1=%f, x2=%f, y1=%f, y2=%f", _axisMaximums[0], _axisMaximums[1], _axisMaximums[2], _axisMaximums[3]); + + QRectF newRect(0,0, _view->size().width(), _view->size().height()); + _view->scene()->setSceneRect(newRect); + int borderHor = _horizontalAxis->border(); + int borderVer = _verticalAxis->border(); + + float scaleX = 1; + float scaleY = 1; + float den = 0; + den = axis[1] - axis[0]; + if(den != 0) + { + scaleX = (_view->sceneRect().width()-(borderHor*2)) / den; + } + den = axis[3] - axis[2]; + if(den != 0) + { + scaleY = (_view->sceneRect().height()-(borderVer*2)) / den; + } + + for(QList::iterator i=curves.begin(); i!=curves.end(); ++i) + { + if((*i)->isVisible()) + { + int xDir = 1; + int yDir = -1; + (*i)->update(scaleX, + scaleY, + xDir<0?axis[1]+float(borderHor-2)/scaleX:-(axis[0]-float(borderHor-2)/scaleX), + yDir<0?axis[3]+float(borderVer-2)/scaleY:-(axis[2]-float(borderVer-2)/scaleY), + xDir, + yDir, + _aKeepAllData->isChecked()); + } + } + + //grid + qDeleteAll(hGridLines); + hGridLines.clear(); + qDeleteAll(vGridLines); + vGridLines.clear(); + if(_aShowGrid->isChecked()) + { + borderHor-=2; + borderVer-=2; + // TODO make a PlotGrid class ? + int w = _view->sceneRect().width()-(borderHor*2); + int h = _view->sceneRect().height()-(borderVer*2); + float stepH = w / _horizontalAxis->count(); + float stepV = h / _verticalAxis->count(); + QPen pen(Qt::DashLine); + for(int i=0; i*stepV < h+stepV; i+=5) + { + //horizontal lines + hGridLines.append(_view->scene()->addLine(0, stepV*i+borderVer, borderHor, stepV*i+borderVer)); + hGridLines.append(_view->scene()->addLine(borderHor, stepV*i+borderVer, w+borderHor, stepV*i+borderVer, pen)); + hGridLines.append(_view->scene()->addLine(w+borderHor, stepV*i+borderVer, w+borderHor*2, stepV*i+borderVer)); + } + for(int i=0; i*stepH < w+stepH; i+=5) + { + //vertical lines + vGridLines.append(_view->scene()->addLine(stepH*i+borderHor, 0, stepH*i+borderHor, borderVer)); + vGridLines.append(_view->scene()->addLine(stepH*i+borderHor, borderVer, stepH*i+borderHor, h+borderVer, pen)); + vGridLines.append(_view->scene()->addLine(stepH*i+borderHor, h+borderVer, stepH*i+borderHor, h+borderVer*2)); + } + } + + // Update refresh rate + if(_aShowRefreshRate->isChecked()) + { + int refreshRate = qRound(1000.0f/float(_refreshIntervalTime.restart())); + if(refreshRate > 0 && refreshRate < _lowestRefreshRate) + { + _lowestRefreshRate = refreshRate; + } + // Refresh the label only after each 100 ms + if(_refreshStartTime.elapsed() > 100) + { + _refreshRate->setText(QString::number(_lowestRefreshRate)); + _lowestRefreshRate = 99; + _refreshStartTime.start(); + } + } +} + +void Plot::setFixedXAxis(float x1, float x2) +{ + _fixedAxis[0] = true; + _axisMaximums[0] = x1; + _axisMaximums[1] = x2; +} + +void Plot::setFixedYAxis(float y1, float y2) +{ + _fixedAxis[1] = true; + _axisMaximums[2] = y1; + _axisMaximums[3] = y2; +} + +void Plot::updateAxis(const PlotCurve * curve) +{ + PlotCurve * value = _curves.value(curve, 0); + if(value && value->isVisible() && value->itemsSize() && value->isMinMaxValid()) + { + const QVector & minMax = value->getMinMax(); + //ULOGGER_DEBUG("x1=%f, x2=%f, y1=%f, y2=%f", minMax[0], minMax[1], minMax[2], minMax[3]); + if(minMax.size() != 4) + { + ULOGGER_ERROR("minMax size != 4 ?!?"); + return; + } + this->updateAxis(minMax[0], minMax[1], minMax[2], minMax[3]); + this->update(); + } +} + +bool Plot::updateAxis(float x1, float x2, float y1, float y2) +{ + bool modified = false; + modified = updateAxis(x1,y1); + if(!modified) + { + modified = updateAxis(x2,y2); + } + else + { + updateAxis(x2,y2); + } + return modified; +} + +bool Plot::updateAxis(float x, float y) +{ + //ULOGGER_DEBUG("x=%f, y=%f", x,y); + bool modified = false; + if(!_fixedAxis[0] && (!_axisMaximumsSet[0] || x < _axisMaximums[0])) + { + _axisMaximums[0] = x; + _axisMaximumsSet[0] = true; + modified = true; + } + + if(!_fixedAxis[0] && (!_axisMaximumsSet[1] || x > _axisMaximums[1])) + { + _axisMaximums[1] = x; + _axisMaximumsSet[1] = true; + modified = true; + } + + if(!_fixedAxis[1] && (!_axisMaximumsSet[2] || y < _axisMaximums[2])) + { + _axisMaximums[2] = y; + _axisMaximumsSet[2] = true; + modified = true; + } + + if(!_fixedAxis[1] && (!_axisMaximumsSet[3] || y > _axisMaximums[3])) + { + _axisMaximums[3] = y; + _axisMaximumsSet[3] = true; + modified = true; + } + + return modified; +} + +void Plot::updateAxis() +{ + //Reset the axis + for(int i=0; i<4; ++i) + { + if((!_fixedAxis[0] && i<2) || (!_fixedAxis[1] && i>=2)) + { + _axisMaximums[i] = 0; + _axisMaximumsSet[i] = false; + } + } + + QList curves = _curves.values(); + for(int i=0; iisVisible() && curves.at(i)->isMinMaxValid()) + { + const QVector & minMax = curves.at(i)->getMinMax(); + this->updateAxis(minMax[0], minMax[1], minMax[2], minMax[3]); + } + } + + this->update(); + + this->captureScreen(); +} + +void Plot::paintEvent(QPaintEvent * event) +{ + this->replot(); + QWidget::paintEvent(event); +} + +void Plot::contextMenuEvent(QContextMenuEvent * event) +{ + QAction * action = _menu->exec(event->globalPos()); + + if(!action) + { + return; + } + else if(action == _aShowLegend) + { + this->showLegend(_aShowLegend->isChecked()); + } + else if(action == _aShowGrid) + { + this->showGrid(_aShowGrid->isChecked()); + } + else if(action == _aShowRefreshRate) + { + this->showRefreshRate(_aShowRefreshRate->isChecked()); + } + else if(action == _aKeepAllData) + { + this->keepAllData(_aKeepAllData->isChecked()); + } + else if(action == _aLimit0 || + action == _aLimit10 || + action == _aLimit50 || + action == _aLimit100 || + action == _aLimit500 || + action == _aLimit1000 || + action == _aLimitCustom) + { + this->setMaxVisibleItems(action->text().toInt()); + this->updateAxis(); + } + else if(action == _aAddVerticalLine || action == _aAddHorizontalLine) + { + bool ok; + QString text = QInputDialog::getText(this, action->text(), tr("New line name :"), QLineEdit::Normal, "", &ok); + while(ok && text.isEmpty()) + { + QMessageBox::warning(this, action->text(), tr("The name is not valid or it is already used in this plot.")); + text = QInputDialog::getText(this, action->text(), tr("New line name :"), QLineEdit::Normal, "", &ok); + } + if(ok) + { + double min = _axisMaximums[2]; + double max = _axisMaximums[3]; + QString axis = "Y"; + if(action == _aAddVerticalLine) + { + min = _axisMaximums[0]; + max = _axisMaximums[1]; + axis = "X"; + } + double value = QInputDialog::getDouble(this, + action->text(), + tr("%1 value (min=%2, max=%3):").arg(axis).arg(min).arg(max), + (min+max)/2, + -2147483647, + 2147483647, + 2, + &ok); + if(ok) + { + if(action == _aAddHorizontalLine) + { + this->addThreshold(text, value, Qt::Horizontal); + } + else + { + this->addThreshold(text, value, Qt::Vertical); + } + } + } + } + else if(action == _aChangeTitle) + { + bool ok; + QString text = _title->text(); + if(text.isEmpty()) + { + text = this->objectName(); + } + text = QInputDialog::getText(this, _aChangeTitle->text(), tr("Title :"), QLineEdit::Normal, text, &ok); + if(ok) + { + this->setTitle(text); + } + } + else if(action == _aChangeXLabel) + { + bool ok; + QString text = QInputDialog::getText(this, _aChangeXLabel->text(), tr("X axis label :"), QLineEdit::Normal, _xLabel->text(), &ok); + if(ok) + { + this->setXLabel(text); + } + } + else if(action == _aChangeYLabel) + { + bool ok; + QString text = QInputDialog::getText(this, _aChangeYLabel->text(), tr("Y axis label :"), QLineEdit::Normal, _yLabel->text(), &ok); + if(ok) + { + this->setYLabel(text, _yLabel->orientation()); + } + } + else if(action == _aYLabelVertical) + { + this->setYLabel(_yLabel->text(), _aYLabelVertical->isChecked()?Qt::Vertical:Qt::Horizontal); + } + else if(action == _aSaveFigure) + { + + QString text; +#ifdef QT_SVG_LIB + text = QFileDialog::getSaveFileName(this, tr("Save figure to ..."), (QDir::homePath() + "/") + this->title() + ".png", "*.png *.xpm *.jpg *.pdf *.svg"); +#else + text = QFileDialog::getSaveFileName(this, tr("Save figure to ..."), (QDir::homePath() + "/") + this->title() + ".png", "*.png *.xpm *.jpg *.pdf"); +#endif + if(!text.isEmpty()) + { + bool flatModified = false; + if(!_legend->isFlat()) + { + _legend->setFlat(true); + flatModified = true; + } + + QPalette p(palette()); + // Set background color to white + QColor c = p.color(QPalette::Background); + p.setColor(QPalette::Background, Qt::white); + setPalette(p); + +#ifdef QT_SVG_LIB + if(QFileInfo(text).suffix().compare("svg") == 0) + { + QSvgGenerator generator; + generator.setFileName(text); + generator.setSize(this->size()); + QPainter painter; + painter.begin(&generator); + this->render(&painter); + painter.end(); + } + else + { +#endif + if(QFileInfo(text).suffix().compare("pdf") == 0) + { + QPrinter printer; + printer.setOutputFormat(QPrinter::PdfFormat); + printer.setOutputFileName(text); + this->render(&printer); + } + else + { + QPixmap figure = QPixmap::grabWidget(this); + figure.save(text); + } +#ifdef QT_SVG_LIB + } +#endif + // revert background color + p.setColor(QPalette::Background, c); + setPalette(p); + + if(flatModified) + { + _legend->setFlat(false); + } + } + } + else if(action == _aAutoScreenCapture) + { + this->captureScreen(); + /*if(_aAutoScreenCapture->isChecked()) + { + bool ok; + int value = QInputDialog::getInt(this, + action->text(), + tr("Screen capture delay (ms) [min=100]:"), + _autoScreenCaptureDelay, + 100, + 2147483647, + 100, + &ok); + if(ok) + { + _autoScreenCaptureDelay = value; + this->autoCaptureScreen(); + } + else + { + _aAutoScreenCapture->setChecked(false); + } + }*/ + } + else if(action == _aClearData) + { + QList curves = _curves.values(); + for(int i=0; i(curves.at(i)) == 0) + { + curves.at(i)->clear(); + } + } + this->update(); + } + else + { + ULOGGER_WARN("Unknown action"); + } +} + +void Plot::setWorkingDirectory(const QString & workingDirectory) +{ + if(QDir(_workingDirectory).exists()) + { + _workingDirectory = workingDirectory; + } + else + { + ULOGGER_ERROR("The directory \"%s\" doesn't exist", workingDirectory.toStdString().c_str()); + } +} + +void Plot::captureScreen() +{ + if(!_aAutoScreenCapture->isChecked()) + { + return; + } + QString targetDir = _workingDirectory + "/ScreensCaptured"; + QDir dir; + if(!dir.exists(targetDir)) + { + dir.mkdir(targetDir); + } + targetDir += "/"; + targetDir += this->title().replace(" ", "_"); + if(!dir.exists(targetDir)) + { + dir.mkdir(targetDir); + } + targetDir += "/"; + QString name = QDateTime::currentDateTime().toString("yyMMdd-hhmmsszzz") + ".png"; + QPixmap figure = QPixmap::grabWidget(this); + figure.save(targetDir + name); +} + +// for convenience... +ThresholdCurve * Plot::addThreshold(const QString & name, float value, Qt::Orientation orientation) +{ + ThresholdCurve * curve = new ThresholdCurve(name, value, orientation, this); + QPen pen = curve->pen(); + pen.setStyle((Qt::PenStyle)(_penStyleCount++ % 4 + 2)); + curve->setPen(pen); + if(!this->addCurve(curve)) + { + if(curve) + { + delete curve; + } + } + else + { + this->update(); + } + return curve; +} + +void Plot::setTitle(const QString & text) +{ + _title->setText(text); + _title->setVisible(!text.isEmpty()); + this->update(); +} + +void Plot::setXLabel(const QString & text) +{ + _xLabel->setText(text); + _xLabel->setVisible(!text.isEmpty()); + this->update(); +} + +void Plot::setYLabel(const QString & text, Qt::Orientation orientation) +{ + _yLabel->setText(text); + _yLabel->setOrientation(orientation); + _yLabel->setVisible(!text.isEmpty()); + _aYLabelVertical->setChecked(orientation==Qt::Vertical); + this->update(); +} + +QGraphicsScene * Plot::scene() const +{ + return _view->scene(); +} + +void Plot::showLegend(bool shown) +{ + _legend->setVisible(shown); + _aShowLegend->setChecked(shown); + this->update(); +} + +void Plot::showGrid(bool shown) +{ + _aShowGrid->setChecked(shown); + this->update(); +} + +void Plot::showRefreshRate(bool shown) +{ + _aShowRefreshRate->setChecked(shown); + _refreshRate->setVisible(shown); + this->update(); +} + +void Plot::keepAllData(bool kept) +{ + _aKeepAllData->setChecked(kept); +} + +void Plot::setMaxVisibleItems(int maxVisibleItems) +{ + if(maxVisibleItems <= 0) + { + _aLimit0->setChecked(true); + } + else if(maxVisibleItems == 10) + { + _aLimit10->setChecked(true); + } + else if(maxVisibleItems == 50) + { + _aLimit50->setChecked(true); + } + else if(maxVisibleItems == 100) + { + _aLimit100->setChecked(true); + } + else if(maxVisibleItems == 500) + { + _aLimit500->setChecked(true); + } + else if(maxVisibleItems == 1000) + { + _aLimit1000->setChecked(true); + } + else + { + _aLimitCustom->setVisible(true); + _aLimitCustom->setChecked(true); + _aLimitCustom->setText(QString::number(maxVisibleItems)); + } + _maxVisibleItems = maxVisibleItems; +} + +void Plot::removeCurves() +{ + QList tmp = _curves.values(); + for(QList::iterator iter=tmp.begin(); iter!=tmp.end(); ++iter) + { + this->removeCurve(*iter); + } + _curves.clear(); +} + +void Plot::removeCurve(const PlotCurve * curve) +{ + PlotCurve * c = _curves.value(curve, 0); + ULOGGER_DEBUG("Plot=\"%s\" removing curve=\"%s\"", this->objectName().toStdString().c_str(), curve?curve->name().toStdString().c_str():""); + if(c) + { + c->detach(this); + _curves.remove(c); + _legend->removeLegendItem(c); + if(c->parent() == this) + { + delete c; + } + // Update axis + updateAxis(); + } +} + +void Plot::showCurve(const PlotCurve * curve, bool shown) +{ + PlotCurve * value = _curves.value(curve, 0); + if(value) + { + if(value->isVisible() != shown) + { + value->setVisible(shown); + this->updateAxis(); + } + } +} + +#ifndef PLOT_WIDGET_OUT_OF_LIB +} +#endif diff --git a/guilib/src/Plot.h b/guilib/src/Plot.h new file mode 100644 index 0000000000..5566be8124 --- /dev/null +++ b/guilib/src/Plot.h @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef PLOT_H_ +#define PLOT_H_ + +// Uncomment this to disable all references to RTAB-Map and UtiLite dependencies +//#define PLOT_WIDGET_OUT_OF_LIB + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class QGraphicsView; +class QGraphicsScene; +class QGraphicsItem; +class QFormLayout; + +#ifndef PLOT_WIDGET_OUT_OF_LIB +namespace rtabmap { +#endif + +class PlotItem : public QGraphicsEllipseItem +{ +public: + PlotItem(qreal dataX, qreal dataY, qreal width=2); + PlotItem(const QPointF & data, qreal width=2); + virtual ~PlotItem(); + +public: + void setNextItem(PlotItem * nextItem); + void setPreviousItem(PlotItem * previousItem); + void setData(const QPointF & data); + + PlotItem * nextItem() const {return _nextItem;} + PlotItem * previousItem() const {return _previousItem;}; + const QPointF & data() const {return _data;} + +protected: + virtual void hoverEnterEvent(QGraphicsSceneHoverEvent * event); + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent * event); + virtual void focusInEvent(QFocusEvent * event); + virtual void focusOutEvent(QFocusEvent * event); + virtual void keyReleaseEvent(QKeyEvent * keyEvent); + + virtual void showDescription(bool shown); + +private: + QPointF _data; + QGraphicsTextItem * _text; + PlotItem * _previousItem; + PlotItem * _nextItem; +}; + +class Plot; + +class PlotCurve : public QObject +{ + Q_OBJECT + +public: + PlotCurve(const QString & name, QObject * parent = 0); + PlotCurve(const QString & name, const QVector data, QObject * parent = 0); + PlotCurve(const QString & name, const QVector & x, const QVector & y, QObject * parent = 0); + virtual ~PlotCurve(); + + const QPen & pen() const {return _pen;} + const QBrush & brush() const {return _brush;} + void setPen(const QPen & pen); + void setBrush(const QBrush & brush); + + void setDefaultStepX(float stepX) {_defaultStepX = stepX;} + QString name() const {return _name;} + int itemsSize(); + QPointF getItemData(int index); + void setStartX(float startX) {_startX = startX;} + bool isVisible() const {return _visible;} + void setData(QVector & data); // take the ownership + void setData(const QVector & x, const QVector & y); + void getData(QVector & x, QVector & y) const; // only call in Qt MainThread + +public slots: + virtual void clear(); + void setVisible(bool visible); + void addValue(PlotItem * data); // take the ownership + void addValue(float y); + void addValue(float x, float y); + void addValue(const QString & y); + void addValues(QVector & data); // take the ownership + void addValues(const QVector & xs, const QVector & ys); + void addValues(const QVector & ys); + +signals: + void dataChanged(const PlotCurve *); + +protected: + friend class Plot; + void attach(Plot * plot); + void detach(Plot * plot); + void updateMinMax(); + const QVector & getMinMax() const {return _minMax;} + int removeItem(int index); + void _addValue(PlotItem * data);; + virtual bool isMinMaxValid() const {return _minMax.size();} + virtual void update(float scaleX, float scaleY, float offsetX, float offsetY, int xDir, int yDir, bool allDataKept); + QList _items; + Plot * _plot; + +private: + void removeItem(PlotItem * item); + +private: + QString _name; + QPen _pen; + QBrush _brush; + float _defaultStepX; + float _startX; + bool _visible; + bool _valuesShown; + QVector _minMax; // minX, maxX, minY, maxY +}; + + +class ThresholdCurve : public PlotCurve +{ + Q_OBJECT + +public: + ThresholdCurve(const QString & name, float thesholdValue, Qt::Orientation orientation = Qt::Horizontal, QObject * parent = 0); + ~ThresholdCurve(); + +public slots: + void setThreshold(float threshold); + void setOrientation(Qt::Orientation orientation); + +protected: + friend class Plot; + virtual void update(float scaleX, float scaleY, float offsetX, float offsetY, int xDir, int yDir, bool allDataKept); + virtual bool isMinMaxValid() const {return false;} + +private: + Qt::Orientation _orientation; +}; + + +class PlotAxis : public QWidget +{ +public: + PlotAxis(Qt::Orientation orientation = Qt::Horizontal, float min=0, float max=1, QWidget * parent = 0); + virtual ~PlotAxis(); + +public: + void setAxis(float & min, float & max); + int border() const {return _border;} + int step() const {return _step;} + int count() const {return _count;} + void setReversed(bool reversed); // Vertical :bottom->up, horizontal :right->left + +protected: + virtual void paintEvent(QPaintEvent * event); + +private: + Qt::Orientation _orientation; + float _min; + float _max; + int _count; + int _step; + bool _reversed; + int _gradMaxDigits; + int _border; +}; + + +class PlotLegendItem : public QPushButton +{ + Q_OBJECT + +public: + PlotLegendItem(const PlotCurve * curve, QWidget * parent = 0); + virtual ~PlotLegendItem(); + const PlotCurve * curve() const {return _curve;} + +signals: + void legendItemRemoved(const PlotCurve *); + +protected: + virtual void contextMenuEvent(QContextMenuEvent * event); + +private: + const PlotCurve * _curve; + QMenu * _menu; + QAction * _aChangeText; + QAction * _aResetText; + QAction * _aRemoveCurve; + QAction * _aCopyToClipboard; +}; + + +class PlotLegend : public QWidget +{ + Q_OBJECT + +public: + PlotLegend(QWidget * parent = 0); + virtual ~PlotLegend(); + + void setFlat(bool on); + bool isFlat() const {return _flat;} + void addItem(const PlotCurve * curve); + QPixmap createSymbol(const QPen & pen, const QBrush & brush); + +public slots: + void removeLegendItem(const PlotCurve * curve); + +signals: + void legendItemRemoved(const PlotCurve * curve); + void legendItemToggled(const PlotCurve * curve, bool toggled); + +protected: + virtual void contextMenuEvent(QContextMenuEvent * event); + +private slots: + void redirectToggled(bool); + +private: + bool _flat; + QMenu * _menu; + QAction * _aUseFlatButtons; +}; + + +class OrientableLabel : public QLabel +{ + Q_OBJECT + +public: + OrientableLabel(const QString & text, Qt::Orientation orientation = Qt::Horizontal, QWidget * parent = 0); + virtual ~OrientableLabel(); + Qt::Orientation orientation() const {return _orientation;} + void setOrientation(Qt::Orientation orientation); + QSize sizeHint() const; + QSize minimumSizeHint() const; +protected: + virtual void paintEvent(QPaintEvent* event); +private: + Qt::Orientation _orientation; +}; + +/* + * TODO It could be cool to right-click on a dot in the + * plot to create a new plot to monitor the dot value changes. + */ +class Plot : public QWidget +{ + Q_OBJECT + +public: + Plot(QWidget * parent = 0); + virtual ~Plot(); + + PlotCurve * addCurve(const QString & curveName); + bool addCurve(PlotCurve * curve); + QStringList curveNames(); + bool contains(const QString & curveName); + void removeCurves(); + ThresholdCurve * addThreshold(const QString & name, float value, Qt::Orientation orientation = Qt::Horizontal); + QString title() const {return this->objectName();} + QPen getRandomPenColored(); + void showLegend(bool shown); + void showGrid(bool shown); + void showRefreshRate(bool shown); + void keepAllData(bool kept); + void showXAxis(bool shown) {_horizontalAxis->setVisible(shown);} + void showYAxis(bool shown) {_verticalAxis->setVisible(shown);} + void setVariableXAxis() {_fixedAxis[0] = false;} + void setVariableYAxis() {_fixedAxis[1] = false;} + void setFixedXAxis(float x1, float x2); + void setFixedYAxis(float y1, float y2); + void setMaxVisibleItems(int maxVisibleItems); + void setTitle(const QString & text); + void setXLabel(const QString & text); + void setYLabel(const QString & text, Qt::Orientation orientation = Qt::Vertical); + QGraphicsScene * scene() const; + void setWorkingDirectory(const QString & workingDirectory); + +public slots: + void removeCurve(const PlotCurve * curve); + void showCurve(const PlotCurve * curve, bool shown); + +private slots: + void captureScreen(); + void updateAxis(const PlotCurve * curve); + void updateAxis(); //reset axis and recompute it with all curves minMax + +protected: + virtual void contextMenuEvent(QContextMenuEvent * event); + virtual void paintEvent(QPaintEvent * event); + +private: + void replot(); + bool updateAxis(float x, float y); + bool updateAxis(float x1, float x2, float y1, float y2); + void setupUi(); + void createActions(); + void createMenus(); + + +private: + PlotLegend * _legend; + QGraphicsView * _view; + float _axisMaximums[4]; // {x1->x2, y1->y2} + bool _axisMaximumsSet[4]; // {x1->x2, y1->y2} + bool _fixedAxis[2]; + PlotAxis * _verticalAxis; + PlotAxis * _horizontalAxis; + int _penStyleCount; + int _maxVisibleItems; + QList hGridLines; + QList vGridLines; + QMap _curves; + QLabel * _title; + QLabel * _xLabel; + OrientableLabel * _yLabel; + QLabel * _refreshRate; + QString _workingDirectory; + QTime _refreshIntervalTime; + int _lowestRefreshRate; + QTime _refreshStartTime; + + QMenu * _menu; + QAction * _aShowLegend; + QAction * _aShowGrid; + QAction * _aKeepAllData; + QAction * _aLimit0; + QAction * _aLimit10; + QAction * _aLimit50; + QAction * _aLimit100; + QAction * _aLimit500; + QAction * _aLimit1000; + QAction * _aLimitCustom; + QAction * _aAddVerticalLine; + QAction * _aAddHorizontalLine; + QAction * _aChangeTitle; + QAction * _aChangeXLabel; + QAction * _aChangeYLabel; + QAction * _aYLabelVertical; + QAction * _aShowRefreshRate; + QAction * _aSaveFigure; + QAction * _aAutoScreenCapture; + QAction * _aClearData; +}; + +#ifndef PLOT_WIDGET_OUT_OF_LIB +} +#endif + +#endif /* PLOT_H_ */ diff --git a/guilib/src/PreferencesDialog.cpp b/guilib/src/PreferencesDialog.cpp new file mode 100644 index 0000000000..040e178e09 --- /dev/null +++ b/guilib/src/PreferencesDialog.cpp @@ -0,0 +1,1651 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "rtabmap/gui/PreferencesDialog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "utilite/ULogger.h" +#include "ui_preferencesDialog.h" +#include "rtabmap/core/Rtabmap.h" +#include "rtabmap/core/Parameters.h" +#include "utilite/UConversion.h" +#include "Plot.h" +#include "utilite/UStl.h" + +#define DEFAULT_GUI_IMAGES_KEPT true +#define DEFAULT_LOGGER_LEVEL 1 +#define DEFAULT_LOGGER_EVENT_LEVEL 3 +#define DEFAULT_LOGGER_PAUSE_LEVEL 4 +#define DEFAULT_LOGGER_PRINT_TIME true + +using namespace rtabmap; + +namespace rtabmap { + +PreferencesDialog::PreferencesDialog(QWidget * parent) : + QDialog(parent), + _obsoletePanels(kPanelDummy), + _ui(0), + _indexModel(0), + _initialized(false), + _predictionPanelInitialized(false) +{ + ULOGGER_DEBUG(""); + + _ui = new Ui_preferencesDialog(); + _ui->setupUi(this); + + _ui->predictionPlot->showLegend(false); + + // Connect + connect(_ui->buttonBox_global, SIGNAL(clicked(QAbstractButton *)), this, SLOT(closeDialog(QAbstractButton *))); + connect(_ui->buttonBox_local, SIGNAL(clicked(QAbstractButton *)), this, SLOT(resetApply(QAbstractButton *))); + connect(_ui->pushButton_loadConfig, SIGNAL(clicked()), this, SLOT(loadConfigFrom())); + connect(_ui->pushButton_saveConfig, SIGNAL(clicked()), this, SLOT(saveConfigTo())); + + // General panel + connect(_ui->general_checkBox_imagesKept, SIGNAL(stateChanged(int)), this, SLOT(makeObsoleteGeneralPanel())); + connect(_ui->comboBox_loggerLevel, SIGNAL(currentIndexChanged(int)), this, SLOT(makeObsoleteGeneralPanel())); + connect(_ui->comboBox_loggerEventLevel, SIGNAL(currentIndexChanged(int)), this, SLOT(makeObsoleteGeneralPanel())); + connect(_ui->checkBox_logger_printTime, SIGNAL(stateChanged(int)), this, SLOT(makeObsoleteGeneralPanel())); + connect(_ui->checkBox_verticalLayoutUsed, SIGNAL(stateChanged(int)), this, SLOT(makeObsoleteGeneralPanel())); + connect(_ui->checkBox_imageFlipped, SIGNAL(stateChanged(int)), this, SLOT(makeObsoleteGeneralPanel())); + connect(_ui->comboBox_loggerType, SIGNAL(currentIndexChanged(int)), this, SLOT(makeObsoleteGeneralPanel())); + connect(_ui->checkBox_imageRejectedShown, SIGNAL(stateChanged(int)), this, SLOT(makeObsoleteGeneralPanel())); + connect(_ui->checkBox_beep, SIGNAL(stateChanged(int)), this, SLOT(makeObsoleteGeneralPanel())); + connect(_ui->horizontalSlider_keypointsOpacity, SIGNAL(valueChanged(int)), this, SLOT(makeObsoleteGeneralPanel())); + + //Source panel + _ui->stackedWidget_2->setCurrentIndex(_ui->source_comboBox_type->currentIndex()); + connect(_ui->source_comboBox_type, SIGNAL(currentIndexChanged(int)), _ui->stackedWidget_2, SLOT(setCurrentIndex(int))); + connect(_ui->source_comboBox_type, SIGNAL(currentIndexChanged(int)), this, SLOT(makeObsoleteSourcePanel())); + connect(_ui->general_doubleSpinBox_imgRate, SIGNAL(valueChanged(double)), this, SLOT(makeObsoleteSourcePanel())); + connect(_ui->general_checkBox_autoRestart, SIGNAL(stateChanged(int)), this, SLOT(makeObsoleteSourcePanel())); + //usbDevice group + connect(_ui->source_usbDevice_spinBox_id, SIGNAL(valueChanged(int)), this, SLOT(makeObsoleteSourcePanel())); + connect(_ui->source_spinBox_imgWidth, SIGNAL(valueChanged(int)), this, SLOT(makeObsoleteSourcePanel())); + connect(_ui->source_spinBox_imgheight, SIGNAL(valueChanged(int)), this, SLOT(makeObsoleteSourcePanel())); + //images group + connect(_ui->source_images_toolButton_selectSource, SIGNAL(clicked()), this, SLOT(selectSource())); + connect(_ui->source_images_lineEdit_path, SIGNAL(textChanged(const QString &)), this, SLOT(makeObsoleteSourcePanel())); + connect(_ui->source_images_spinBox_startPos, SIGNAL(valueChanged(int)), this, SLOT(makeObsoleteSourcePanel())); + connect(_ui->source_images_refreshDir, SIGNAL(stateChanged(int)), this, SLOT(makeObsoleteSourcePanel())); + //video group + connect(_ui->source_video_toolButton_selectSource, SIGNAL(clicked()), this, SLOT(selectSource())); + connect(_ui->source_video_lineEdit_path, SIGNAL(textChanged(const QString &)), this, SLOT(makeObsoleteSourcePanel())); + //database group + connect(_ui->source_database_toolButton_selectSource, SIGNAL(clicked()), this, SLOT(selectSource())); + connect(_ui->source_database_lineEdit_path, SIGNAL(textChanged(const QString &)), this, SLOT(makeObsoleteSourcePanel())); + connect(_ui->source_database_checkBox_ignoreChildren, SIGNAL(stateChanged(int)), this, SLOT(makeObsoleteSourcePanel())); + + + // Map objects name with the corresponding parameter key, needed for the addParameter() slots + //Rtabmap + _ui->general_doubleSpinBox_reactivationThr->setObjectName(Parameters::kRtabmapReactivationThr().c_str()); + _ui->general_checkBox_publishStats->setObjectName(Parameters::kRtabmapPublishStats().c_str()); + _ui->general_doubleSpinBox_timeThr->setObjectName(Parameters::kRtabmapTimeThr().c_str()); + _ui->general_checkBox_disableReactivation->setObjectName(Parameters::kRtabmapDisableReactivation().c_str()); + _ui->general_spinBox_imagesBufferSize->setObjectName(Parameters::kRtabmapSMStateBufferSize().c_str()); + _ui->general_spinBox_minMemorySizeForLoopDetection->setObjectName(Parameters::kRtabmapMinMemorySizeForLoopDetection().c_str()); + _ui->general_spinBox_maxRetrieved->setObjectName(Parameters::kRtabmapMaxRetrieved().c_str()); + _ui->lineEdit_workingDirectory->setObjectName(Parameters::kRtabmapWorkingDirectory().c_str()); + connect(_ui->toolButton_workingDirectory, SIGNAL(clicked()), this, SLOT(changeWorkingDirectory())); + + // Memory + _ui->general_checkBox_keepRawData->setObjectName(Parameters::kMemRawDataKept().c_str()); + _ui->general_spinBox_maxStMemSize->setObjectName(Parameters::kMemMaxStMemSize().c_str()); + _ui->general_checkBox_similarityOnlyLast->setObjectName(Parameters::kMemSimilarityOnlyLast().c_str()); + _ui->general_doubleSpinBox_similarityThr->setObjectName(Parameters::kMemSimilarityThr().c_str()); + _ui->general_checkBox_incrementalMemory->setObjectName(Parameters::kMemIncrementalMemory().c_str()); + _ui->general_checkBox_commonSignatureUsed->setObjectName(Parameters::kMemCommonSignatureUsed().c_str()); + _ui->general_checkBox_databaseCleaned->setObjectName(Parameters::kMemDatabaseCleaned().c_str()); + _ui->mem_spinBox_delayRequired->setObjectName(Parameters::kMemDelayRequired().c_str()); + _ui->general_checkBox_localGraphCleaned->setObjectName(Parameters::kRtabmapLocalGraphCleaned().c_str()); + _ui->general_doubleSpinBox_recentWmRatio->setObjectName(Parameters::kMemRecentWmRatio().c_str()); + + // Database + _ui->spinBox_dbMinSignToSave->setObjectName(Parameters::kDbMinSignaturesToSave().c_str()); + _ui->spinBox_dbMinWordsToSave->setObjectName(Parameters::kDbMinWordsToSave().c_str()); + _ui->checkBox_dbInMemory->setObjectName(Parameters::kDbSqlite3InMemory().c_str()); + _ui->spinBox_dbCacheSize->setObjectName(Parameters::kDbSqlite3CacheSize().c_str()); + _ui->comboBox_dbJournalMode->setObjectName(Parameters::kDbSqlite3JournalMode().c_str()); + + // Create hypotheses + _ui->general_doubleSpinBox_hardThr->setObjectName(Parameters::kRtabmapLoopThr().c_str()); + _ui->general_doubleSpinBox_loopRatio->setObjectName(Parameters::kRtabmapLoopRatio().c_str()); + + //Bayes + _ui->general_doubleSpinBox_vp->setObjectName(Parameters::kBayesVirtualPlacePriorThr().c_str()); + _ui->lineEdit_bayes_predictionLC->setObjectName(Parameters::kBayesPredictionLC().c_str()); + + //Keypoint-based + _ui->comboBox_dictionary_strategy->setObjectName(Parameters::kKpNNStrategy().c_str()); + _ui->checkBox_dictionary_incremental->setObjectName(Parameters::kKpIncrementalDictionary().c_str()); + _ui->comboBox_detector_strategy->setObjectName(Parameters::kKpDetectorStrategy().c_str()); + _ui->comboBox_descriptor_strategy->setObjectName(Parameters::kKpDescriptorStrategy().c_str()); + _ui->checkBox_dictionary_minDistUsed->setObjectName(Parameters::kKpMinDistUsed().c_str()); + _ui->surf_doubleSpinBox_matchThr->setObjectName(Parameters::kKpMinDist().c_str()); + _ui->checkBox_dictionary_nndrUsed->setObjectName(Parameters::kKpNndrUsed().c_str()); + _ui->surf_doubleSpinBox_nndrRatio->setObjectName(Parameters::kKpNndrRatio().c_str()); + _ui->surf_spinBox_maxLeafs->setObjectName(Parameters::kKpMaxLeafs().c_str()); + _ui->surf_spinBox_wordsPerImageTarget->setObjectName(Parameters::kKpWordsPerImage().c_str()); + _ui->surf_doubleSpinBox_ratioBadSign->setObjectName(Parameters::kKpBadSignRatio().c_str()); + _ui->checkBox_kp_usingAdaptiveResponseThr->setObjectName(Parameters::kKpUsingAdaptiveResponseThr().c_str()); + _ui->general_checkBox_reactivatedWordsComparedToNewWords->setObjectName(Parameters::kKpReactivatedWordsComparedToNewWords().c_str()); + _ui->checkBox_kp_tfIdfLikelihoodUsed->setObjectName(Parameters::kKpTfIdfLikelihoodUsed().c_str()); + _ui->checkBox_kp_tfIdfNormalized->setObjectName(Parameters::kKpTfIdfNormalized().c_str()); + _ui->checkBox_kp_parallelized->setObjectName(Parameters::kKpParallelized().c_str()); + _ui->lineEdit_kp_roi->setObjectName(Parameters::kKpRoiRatios().c_str()); + _ui->lineEdit_dictionaryPath->setObjectName(Parameters::kKpDictionaryPath().c_str()); + connect(_ui->toolButton_dictionaryPath, SIGNAL(clicked()), this, SLOT(changeDictionaryPath())); + + //SURF detector + _ui->surf_doubleSpinBox_hessianThr->setObjectName(Parameters::kSURFHessianThreshold().c_str()); + _ui->surf_spinBox_octaves->setObjectName(Parameters::kSURFOctaves().c_str()); + _ui->surf_spinBox_octaveLayers->setObjectName(Parameters::kSURFOctaveLayers().c_str()); + _ui->checkBox_surfExtended->setObjectName(Parameters::kSURFExtended().c_str()); + _ui->surf_checkBox_gpuVersion->setObjectName(Parameters::kSURFGpuVersion().c_str()); + _ui->surf_checkBox_upright->setObjectName(Parameters::kSURFUpright().c_str()); + + //Star detector + _ui->star_spinBox_maxSize->setObjectName(Parameters::kStarMaxSize().c_str()); + _ui->star_spinBox_responseThr->setObjectName(Parameters::kStarResponseThreshold().c_str()); + _ui->star_spinBox_lineThrProj->setObjectName(Parameters::kStarLineThresholdProjected().c_str()); + _ui->star_spinBox_lineThrBin->setObjectName(Parameters::kStarLineThresholdBinarized().c_str()); + _ui->star_spinBox_suppressNonmaxSize->setObjectName(Parameters::kStarSuppressNonmaxSize().c_str()); + + //SIFT detector + _ui->sift_doubleSpinBox_Thr->setObjectName(Parameters::kSIFTThreshold().c_str()); + _ui->sift_doubleSpinBox_edgeThr->setObjectName(Parameters::kSIFTEdgeThreshold().c_str()); + + // verifyHypotheses + _ui->comboBox_vh_strategy->setObjectName(Parameters::kRtabmapVhStrategy().c_str()); + _ui->surf_spinBox_matchCountMinAccepted->setObjectName(Parameters::kVhEpMatchCountMin().c_str()); + _ui->surf_doubleSpinBox_ransacParam1->setObjectName(Parameters::kVhEpRansacParam1().c_str()); + _ui->surf_doubleSpinBox_ransacParam2->setObjectName(Parameters::kVhEpRansacParam2().c_str()); + + setupSignals(); + // custom signals + connect(_ui->doubleSpinBox_kp_roi0, SIGNAL(valueChanged(double)), this, SLOT(updateKpROI())); + connect(_ui->doubleSpinBox_kp_roi1, SIGNAL(valueChanged(double)), this, SLOT(updateKpROI())); + connect(_ui->doubleSpinBox_kp_roi2, SIGNAL(valueChanged(double)), this, SLOT(updateKpROI())); + connect(_ui->doubleSpinBox_kp_roi3, SIGNAL(valueChanged(double)), this, SLOT(updateKpROI())); + + //Create a model from the stacked widgets + // This will add all parameters to the parameters Map + _ui->stackedWidget->setCurrentIndex(0); + this->setupTreeView(); + connect(_ui->treeView, SIGNAL(clicked(QModelIndex)), this, SLOT(clicked(QModelIndex))); + _ui->treeView->expandToDepth(1); + + this->readSettings(); + this->writeSettings();// This will create the ini file if not exist + + this->loadWindowGeometry("PreferencesDialog", this); + + _obsoletePanels = kPanelAll; + _initialized = true; +} + +PreferencesDialog::~PreferencesDialog() { + this->saveWindowGeometry("PreferencesDialog", this); + delete _ui; +} + +void PreferencesDialog::setupTreeView() +{ + QFile modelFile(":/resources/PreferencesModel.txt"); + if(modelFile.open(QIODevice::ReadOnly | QIODevice::Text)) + { + _indexModel = new QStandardItemModel(this); + // Parse the model + QList boxes = this->getGroupBoxes(); + QStandardItem * parentItem = _indexModel->invisibleRootItem(); + int index = 0; + this->parseModel(boxes, parentItem, 0, index); // recursive method + if(index != _ui->stackedWidget->count()) + { + ULOGGER_ERROR("The tree model is not the same size of the stacked widgets...%d vs %d stacks", index, _ui->stackedWidget->count()); + } + _ui->treeView->setModel(_indexModel); + } + else + { + ULOGGER_ERROR("Can't open resource file \"PreferencesModel.txt\""); + } +} + +// recursive... +bool PreferencesDialog::parseModel(QList & boxes, QStandardItem * parentItem, int currentLevel, int & absoluteIndex) +{ + if(parentItem == 0) + { + ULOGGER_ERROR("Parent item is null !"); + return false; + } + + QStandardItem * currentItem = 0; + while(absoluteIndex < boxes.size()) + { + QString title = boxes.at(absoluteIndex)->title(); + bool ok = false; + int lvl = QString(title.at(0)).toInt(&ok); + if(!ok) + { + ULOGGER_ERROR("Error while parsing the first number of the QGroupBox title, the first character must be the number in the hierarchy"); + return false; + } + + + if(lvl == currentLevel) + { + title.remove(0, 1); + boxes.at(absoluteIndex)->setTitle(title); + QStandardItem * item = new QStandardItem(title); + item->setData(absoluteIndex); + currentItem = item; + //ULOGGER_DEBUG("PreferencesDialog::parseModel() lvl(%d) Added %s", currentLevel, title.toStdString().c_str()); + parentItem->appendRow(item); + ++absoluteIndex; + } + else if(lvl > currentLevel) + { + if(lvl>currentLevel+1) + { + ULOGGER_ERROR("Intermediary lvl doesn't exist, lvl %d to %d, indexes %d and %d", currentLevel, lvl, absoluteIndex-1, absoluteIndex); + return false; + } + else + { + parseModel(boxes, currentItem, currentLevel+1, absoluteIndex); // recursive + } + } + else + { + return false; + } + } + return true; +} + +void PreferencesDialog::setupSignals() +{ + const rtabmap::ParametersMap & parameters = Parameters::getDefaultParameters(); + for(rtabmap::ParametersMap::const_iterator iter=parameters.begin(); iter!=parameters.end(); ++iter) + { + QObject * obj = _ui->stackedWidget->findChild((*iter).first.c_str()); + if(obj) + { + QSpinBox * spin = qobject_cast(obj); + QDoubleSpinBox * doubleSpin = qobject_cast(obj); + QComboBox * combo = qobject_cast(obj); + QCheckBox * check = qobject_cast(obj); + QLineEdit * lineEdit = qobject_cast(obj); + if(spin) + { + connect(spin, SIGNAL(valueChanged(int)), this, SLOT(addParameter(int))); + } + else if(doubleSpin) + { + connect(doubleSpin, SIGNAL(valueChanged(double)), this, SLOT(addParameter(double))); + } + else if(combo) + { + connect(combo, SIGNAL(currentIndexChanged(int)), this, SLOT(addParameter(int))); + } + else if(check) + { + connect(check, SIGNAL(stateChanged(int)), this, SLOT(addParameter(int))); + } + else if(lineEdit) + { + connect(lineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(addParameter(const QString &))); + } + else + { + ULOGGER_WARN("QObject called %s can't be cast to a supported widget", (*iter).first.c_str()); + } + } + else + { + ULOGGER_WARN("Can't find the related QObject for parameter %s", (*iter).first.c_str()); + } + } +} + +void PreferencesDialog::clicked(const QModelIndex &index) + { + QStandardItem * item = _indexModel->itemFromIndex(index); + if(item) + { + _ui->stackedWidget->setCurrentIndex(item->data().toInt()); + } + } + +void PreferencesDialog::closeDialog ( QAbstractButton * button ) +{ + QDialogButtonBox::ButtonRole role = _ui->buttonBox_global->buttonRole(button); + switch(role) + { + case QDialogButtonBox::RejectRole: + _parameters.clear(); + _obsoletePanels = kPanelDummy; + this->reject(); + break; + + case QDialogButtonBox::AcceptRole: + if(_obsoletePanels & kPanelAll || _parameters.size()) + { + if(validateForm()) + { + writeSettings(); + this->accept(); + } + } + else + { + this->accept(); + } + break; + + default: + break; + } +} + +void PreferencesDialog::resetApply ( QAbstractButton * button ) +{ + QDialogButtonBox::ButtonRole role = _ui->buttonBox_local->buttonRole(button); + switch(role) + { + case QDialogButtonBox::ApplyRole: + if(validateForm()) + { + writeSettings(); + } + break; + + case QDialogButtonBox::ResetRole: + resetSettings(_ui->stackedWidget->currentIndex()); + break; + + default: + break; + } +} + +void PreferencesDialog::resetSettings(int panelNumber) +{ + QList boxes = this->getGroupBoxes(); + if(panelNumber >= 0 && panelNumber < boxes.size()) + { + if(boxes.at(panelNumber)->objectName() == "groupBox_generalSettingsGui") + { + _ui->general_checkBox_imagesKept->setChecked(DEFAULT_GUI_IMAGES_KEPT); + _ui->comboBox_loggerLevel->setCurrentIndex(DEFAULT_LOGGER_LEVEL); + _ui->comboBox_loggerEventLevel->setCurrentIndex(DEFAULT_LOGGER_EVENT_LEVEL); + _ui->comboBox_loggerPauseLevel->setCurrentIndex(DEFAULT_LOGGER_PAUSE_LEVEL); + _ui->checkBox_logger_printTime->setChecked(DEFAULT_LOGGER_PRINT_TIME); + } + else if(boxes.at(panelNumber)->objectName() == "groupBox_source") + { + _ui->general_doubleSpinBox_imgRate->setValue(1.0); + _ui->source_spinBox_imgWidth->setValue(0); + _ui->source_spinBox_imgheight->setValue(0); + _ui->general_checkBox_autoRestart->setChecked(false); + } + else + { + QObjectList children = boxes.at(panelNumber)->children(); + rtabmap::ParametersMap defaults = Parameters::getDefaultParameters(); + std::string key; + for(int i=0; iobjectName().toStdString(); + if(uContains(defaults, key)) + { + this->setParameter(key, defaults.at(key)); + } + } + + if(boxes.at(panelNumber)->findChild(_ui->lineEdit_bayes_predictionLC->objectName())) + { + this->setupPredictionPanel(); + } + + if(boxes.at(panelNumber)->findChild(_ui->lineEdit_kp_roi->objectName())) + { + this->setupKpRoiPanel(); + } + } + } + else + { + ULOGGER_WARN("panel number and the number of stacked widget doesn't match"); + } + +} + +QString PreferencesDialog::getWorkingDirectory() +{ + return _ui->lineEdit_workingDirectory->text(); +} + +QString PreferencesDialog::getIniFilePath() +{ + return Rtabmap::getIniFilePath().c_str(); +} + +void PreferencesDialog::loadConfigFrom() +{ + QString path = QFileDialog::getOpenFileName(this, tr("Load configurations..."), this->getWorkingDirectory(), "*.ini"); + if(!path.isEmpty()) + { + this->readSettings(path); + } +} + +void PreferencesDialog::readSettings(const QString & filePath) +{ + ULOGGER_DEBUG(""); + readGuiSettings(filePath); + readCameraSettings(filePath); + readCoreSettings(filePath); +} + +void PreferencesDialog::readGuiSettings(const QString & filePath) +{ + QString path = getIniFilePath(); + if(!filePath.isEmpty()) + { + path = filePath; + } + QSettings settings(path, QSettings::IniFormat); + settings.beginGroup("Gui"); + settings.beginGroup("General"); + _ui->general_checkBox_imagesKept->setChecked(settings.value("imagesKept", _ui->general_checkBox_imagesKept->isChecked()).toBool()); + _ui->comboBox_loggerLevel->setCurrentIndex(settings.value("loggerLevel", _ui->comboBox_loggerLevel->currentIndex()).toInt()); + _ui->comboBox_loggerEventLevel->setCurrentIndex(settings.value("loggerEventLevel", _ui->comboBox_loggerEventLevel->currentIndex()).toInt()); + _ui->comboBox_loggerPauseLevel->setCurrentIndex(settings.value("loggerPauseLevel", _ui->comboBox_loggerPauseLevel->currentIndex()).toInt()); + _ui->comboBox_loggerType->setCurrentIndex(settings.value("loggerType", _ui->comboBox_loggerType->currentIndex()).toInt()); + _ui->checkBox_logger_printTime->setChecked(settings.value("loggerPrintTime", _ui->checkBox_logger_printTime->isChecked()).toBool()); + _ui->checkBox_verticalLayoutUsed->setChecked(settings.value("verticalLayoutUsed", _ui->checkBox_verticalLayoutUsed->isChecked()).toBool()); + _ui->checkBox_imageFlipped->setChecked(settings.value("imageFlipped", _ui->checkBox_imageFlipped->isChecked()).toBool()); + _ui->checkBox_imageRejectedShown->setChecked(settings.value("imageRejectedShown", _ui->checkBox_imageRejectedShown->isChecked()).toBool()); + _ui->checkBox_beep->setChecked(settings.value("beep", _ui->checkBox_beep->isChecked()).toBool()); + _ui->horizontalSlider_keypointsOpacity->setValue(settings.value("keypointsOpacity", _ui->horizontalSlider_keypointsOpacity->value()).toInt()); + + settings.endGroup(); // General + + settings.endGroup(); // rtabmap +} + +void PreferencesDialog::readCameraSettings(const QString & filePath) +{ + QString path = getIniFilePath(); + if(!filePath.isEmpty()) + { + path = filePath; + } + QSettings settings(path, QSettings::IniFormat); + + settings.beginGroup("Camera"); + _ui->general_doubleSpinBox_imgRate->setValue(settings.value("imgRate", _ui->general_doubleSpinBox_imgRate->value()).toDouble()); + _ui->general_checkBox_autoRestart->setChecked(settings.value("autoRestart", _ui->general_checkBox_autoRestart->isChecked()).toBool()); + _ui->source_comboBox_type->setCurrentIndex(settings.value("type", _ui->source_comboBox_type->currentIndex()).toInt()); + _ui->source_spinBox_imgWidth->setValue(settings.value("imgWidth",_ui->source_spinBox_imgWidth->value()).toInt()); + _ui->source_spinBox_imgheight->setValue(settings.value("imgHeight",_ui->source_spinBox_imgheight->value()).toInt()); + //usbDevice group + settings.beginGroup("usbDevice"); + _ui->source_usbDevice_spinBox_id->setValue(settings.value("id",_ui->source_usbDevice_spinBox_id->value()).toInt()); + settings.endGroup(); // usbDevice + //images group + settings.beginGroup("images"); + _ui->source_images_lineEdit_path->setText(settings.value("path", _ui->source_images_lineEdit_path->text()).toString()); + _ui->source_images_spinBox_startPos->setValue(settings.value("startPos",_ui->source_images_spinBox_startPos->value()).toInt()); + _ui->source_images_refreshDir->setChecked(settings.value("refreshDir",_ui->source_images_refreshDir->isChecked()).toBool()); + settings.endGroup(); // images + //video group + settings.beginGroup("video"); + _ui->source_video_lineEdit_path->setText(settings.value("path", _ui->source_video_lineEdit_path->text()).toString()); + settings.endGroup(); // video + //database group + settings.beginGroup("database"); + _ui->source_database_lineEdit_path->setText(settings.value("path",_ui->source_database_lineEdit_path->text()).toString()); + _ui->source_database_checkBox_ignoreChildren->setChecked(settings.value("ignoreChildren",_ui->source_database_checkBox_ignoreChildren->isChecked()).toBool()); + settings.endGroup(); // usbDevice + settings.endGroup(); // Camera +} + +void PreferencesDialog::readCoreSettings(const QString & filePath) +{ + UDEBUG(""); + QString path = getIniFilePath(); + if(!filePath.isEmpty()) + { + path = filePath; + } + QSettings settings(path, QSettings::IniFormat); + + const rtabmap::ParametersMap & parameters = Parameters::getDefaultParameters(); + settings.beginGroup("Core"); + QStringList keys = settings.allKeys(); + for(rtabmap::ParametersMap::const_iterator iter = parameters.begin(); iter!=parameters.end(); ++iter) + { + QString key((*iter).first.c_str()); + QString value = settings.value(key, "").toString(); + if(!value.isEmpty()) + { + this->setParameter(key.toStdString(), value.toStdString()); + } + else + { + UDEBUG("key.toStdString()=%s", key.toStdString().c_str()); + // Use the default value if the key doesn't exist yet + this->setParameter(key.toStdString(), (*iter).second); + + // Add information about the working directory if not in the config file + if(key.toStdString().compare(Parameters::kRtabmapWorkingDirectory()) == 0) + { + if(!_initialized) + { + QMessageBox::information(this, + tr("Working directory"), + tr("RTAB-Map needs a working directory to put the database.\n\n" + "By default, the directory \"%1\" is used.\n\n" + "The working directory can be changed any time in the " + "preferences menu.").arg( + Parameters::defaultRtabmapWorkingDirectory().c_str())); + } + } + } + } + settings.endGroup(); // Core +} + +void PreferencesDialog::saveConfigTo() +{ + QString path = QFileDialog::getSaveFileName(this, tr("Save configurations..."), this->getWorkingDirectory()+"/config.ini", "*.ini"); + if(!path.isEmpty()) + { + this->writeSettings(path); + } +} + +void PreferencesDialog::writeSettings(const QString & filePath) +{ + writeGuiSettings(filePath); + writeCameraSettings(filePath); + writeCoreSettings(filePath); + + if(_parameters.size()) + { + emit settingsChanged(_parameters); + } + + if(_obsoletePanels) + { + emit settingsChanged(_obsoletePanels); + } + + _parameters.clear(); + _obsoletePanels = kPanelDummy; +} + +void PreferencesDialog::writeGuiSettings(const QString & filePath) +{ + QString path = getIniFilePath(); + if(!filePath.isEmpty()) + { + path = filePath; + } + QSettings settings(path, QSettings::IniFormat); + settings.beginGroup("Gui"); + + settings.beginGroup("General"); + settings.setValue("imagesKept", _ui->general_checkBox_imagesKept->isChecked()); + settings.setValue("loggerLevel", _ui->comboBox_loggerLevel->currentIndex()); + settings.setValue("loggerEventLevel", _ui->comboBox_loggerEventLevel->currentIndex()); + settings.setValue("loggerPauseLevel", _ui->comboBox_loggerPauseLevel->currentIndex()); + settings.setValue("loggerType", _ui->comboBox_loggerType->currentIndex()); + settings.setValue("loggerPrintTime", _ui->checkBox_logger_printTime->isChecked()); + settings.setValue("verticalLayoutUsed", _ui->checkBox_verticalLayoutUsed->isChecked()); + settings.setValue("imageFlipped", _ui->checkBox_imageFlipped->isChecked()); + settings.setValue("imageRejectedShown", _ui->checkBox_imageRejectedShown->isChecked()); + settings.setValue("beep", _ui->checkBox_beep->isChecked()); + settings.setValue("keypointsOpacity", _ui->horizontalSlider_keypointsOpacity->value()); + settings.endGroup(); // General + + settings.endGroup(); // rtabmap +} + +void PreferencesDialog::writeCameraSettings(const QString & filePath) +{ + QString path = getIniFilePath(); + if(!filePath.isEmpty()) + { + path = filePath; + } + QSettings settings(path, QSettings::IniFormat); + settings.beginGroup("Camera"); + + settings.setValue("imgRate", _ui->general_doubleSpinBox_imgRate->value()); + settings.setValue("autoRestart", _ui->general_checkBox_autoRestart->isChecked()); + settings.setValue("type", _ui->source_comboBox_type->currentIndex()); + settings.setValue("imgWidth", _ui->source_spinBox_imgWidth->value()); + settings.setValue("imgHeight", _ui->source_spinBox_imgheight->value()); + //usbDevice group + settings.beginGroup("usbDevice"); + settings.setValue("id", _ui->source_usbDevice_spinBox_id->value()); + settings.endGroup(); //usbDevice + //images group + settings.beginGroup("images"); + settings.setValue("path", _ui->source_images_lineEdit_path->text()); + settings.setValue("startPos", _ui->source_images_spinBox_startPos->value()); + settings.setValue("refreshDir", _ui->source_images_refreshDir->isChecked()); + settings.endGroup(); //images + //video group + settings.beginGroup("video"); + settings.setValue("path", _ui->source_video_lineEdit_path->text()); + settings.endGroup(); //video + //database group + settings.beginGroup("database"); + settings.setValue("path", _ui->source_database_lineEdit_path->text()); + settings.setValue("ignoreChildren", _ui->source_database_checkBox_ignoreChildren->isChecked()); + settings.endGroup(); //usbDevice + + settings.endGroup(); // Camera +} + +void PreferencesDialog::writeCoreSettings(const QString & filePath) +{ + QString path = getIniFilePath(); + if(!filePath.isEmpty()) + { + path = filePath; + } + QSettings settings(path, QSettings::IniFormat); + settings.beginGroup("Core"); + + const rtabmap::ParametersMap & parameters = Parameters::getDefaultParameters(); + for(rtabmap::ParametersMap::const_iterator iter=parameters.begin(); iter!=parameters.end(); ++iter) + { + QObject * obj = _ui->stackedWidget->findChild((*iter).first.c_str()); + if(obj) + { + QSpinBox * spin = qobject_cast(obj); + QDoubleSpinBox * doubleSpin = qobject_cast(obj); + QComboBox * combo = qobject_cast(obj); + QCheckBox * check = qobject_cast(obj); + QLineEdit * lineEdit = qobject_cast(obj); + if(spin) + { + settings.setValue(obj->objectName(), spin->value()); + } + else if(doubleSpin) + { + settings.setValue(obj->objectName(), doubleSpin->value()); + } + else if(combo) + { + settings.setValue(obj->objectName(), combo->currentIndex()); + } + else if(check) + { + settings.setValue(obj->objectName(), uBool2str(check->isChecked()).c_str()); + } + else if(lineEdit) + { + settings.setValue(obj->objectName(), lineEdit->text()); + } + else + { + ULOGGER_WARN("QObject called %s can't be cast to a supported widget", (*iter).first.c_str()); + } + } + else + { + ULOGGER_WARN("Can't find the related QObject for parameter %s", (*iter).first.c_str()); + } + } + settings.endGroup(); // Core +} + +bool PreferencesDialog::validateForm() +{ + if( validatePanelGeneral() && + validatePanelFourier() && + validatePanelSurf() ) + { + return true; + } + + return false; +} + +bool PreferencesDialog::validatePanelGeneral() +{ + /*if(_ui->general_spinBox_hardThr->value() < _ui->general_spinBox_softThr->value()) + { + QMessageBox::warning(this, tr("Settings validator"), tr("The hard threshold must be bigger than the soft threshold.")); + return false; + }*/ + return true; +} + +bool PreferencesDialog::validatePanelFourier() +{ + return true; +} + +bool PreferencesDialog::validatePanelSurf() +{ + return true; +} + +QString PreferencesDialog::getParamMessage() +{ + return tr("Reading parameters from the configuration file..."); +} + +void PreferencesDialog::showEvent ( QShowEvent * event ) +{ + QProgressDialog statusDialog; + statusDialog.setWindowTitle(tr("Read parameters...")); + statusDialog.setLabelText(this->getParamMessage()); + statusDialog.setMaximum(2); + statusDialog.setWindowModality(Qt::WindowModal); + statusDialog.show(); + + this->readSettings(); + statusDialog.setValue(1); + statusDialog.setLabelText(tr("Reading GUI settings...")); + this->setupPredictionPanel(); + this->setupKpRoiPanel(); + statusDialog.setValue(2); +} + +void PreferencesDialog::saveWindowGeometry(const QString & windowName, const QWidget * window) +{ + QSettings settings(getIniFilePath(), QSettings::IniFormat); + settings.beginGroup("Gui"); + settings.beginGroup(windowName); + settings.setValue("geometry", window->saveGeometry()); + settings.endGroup(); // "windowName" + settings.endGroup(); // rtabmap +} + +void PreferencesDialog::loadWindowGeometry(const QString & windowName, QWidget * window) +{ + QByteArray bytes; + QSettings settings(getIniFilePath(), QSettings::IniFormat); + settings.beginGroup("Gui"); + settings.beginGroup(windowName); + bytes = settings.value("geometry", QByteArray()).toByteArray(); + if(!bytes.isEmpty()) + { + window->restoreGeometry(bytes); + } + settings.endGroup(); // "windowName" + settings.endGroup(); // rtabmap +} + +void PreferencesDialog::saveMainWindowState(const QMainWindow * mainWindow) +{ + QSettings settings(getIniFilePath(), QSettings::IniFormat); + settings.beginGroup("Gui"); + settings.beginGroup("MainWindow"); + settings.setValue("state", mainWindow->saveState()); + settings.endGroup(); // "MainWindow" + settings.endGroup(); // rtabmap + + saveWindowGeometry("MainWindow", mainWindow); +} + +void PreferencesDialog::loadMainWindowState(QMainWindow * mainWindow) +{ + QByteArray bytes; + QSettings settings(getIniFilePath(), QSettings::IniFormat); + settings.beginGroup("Gui"); + settings.beginGroup("MainWindow"); + bytes = settings.value("state", QByteArray()).toByteArray(); + if(!bytes.isEmpty()) + { + mainWindow->restoreState(bytes); + } + settings.endGroup(); // "MainWindow" + settings.endGroup(); // rtabmap + + loadWindowGeometry("MainWindow", mainWindow); +} + +void PreferencesDialog::saveCustomConfig(const QString & section, const QString & key, const QString & value) +{ + QSettings settings(getIniFilePath(), QSettings::IniFormat); + settings.beginGroup("Gui"); + settings.beginGroup(section); + settings.setValue(key, value); + settings.endGroup(); // "section" + settings.endGroup(); // rtabmap +} + +QString PreferencesDialog::loadCustomConfig(const QString & section, const QString & key) +{ + QString value; + QSettings settings(getIniFilePath(), QSettings::IniFormat); + settings.beginGroup("Gui"); + settings.beginGroup(section); + value = settings.value(key, QString()).toString(); + settings.endGroup(); // "section" + settings.endGroup(); // rtabmap + return value; +} + +void PreferencesDialog::selectSource(Src src) +{ + ULOGGER_DEBUG("PreferencesDialog::selectSource()"); + + bool fromPrefDialog = false; + //bool modified = false; + if(src == kSrcUndef) + { + fromPrefDialog = true; + if(_ui->source_comboBox_type->currentIndex() == 1) + { + src = kSrcImages; + } + else if(_ui->source_comboBox_type->currentIndex() == 2) + { + src = kSrcVideo; + } + else if(_ui->source_comboBox_type->currentIndex() == 3) + { + src = kSrcDatabase; + } + else + { + src = kSrcUsbDevice; + } + } + + if(src == kSrcImages) + { + QString path = QFileDialog::getExistingDirectory(this, QString(), _ui->source_images_lineEdit_path->text()); + QDir dir(path); + if(!path.isEmpty() && dir.exists()) + { + QStringList filters; + filters << "*.jpg" << "*.ppm" << "*.bmp" << "*.png"; + dir.setNameFilters(filters); + QFileInfoList files = dir.entryInfoList(); + if(!files.empty()) + { + _ui->source_comboBox_type->setCurrentIndex(1); + _ui->source_images_lineEdit_path->setText(path); + _ui->source_images_spinBox_startPos->setValue(1); + _ui->source_images_refreshDir->setChecked(false); + } + else + { + QMessageBox::information(this, + tr("RTAB-Map"), + tr("Images must be one of these formats: ") + filters.join(" ")); + } + } + } + else if(src == kSrcVideo) + { + QString path = QFileDialog::getOpenFileName(this, tr("Select file"), _ui->source_video_lineEdit_path->text(), tr("Videos (*.avi *.mpg)")); + QFile file(path); + if(!path.isEmpty() && file.exists()) + { + _ui->source_comboBox_type->setCurrentIndex(2); + _ui->source_video_lineEdit_path->setText(path); + } + } + else if(src == kSrcDatabase) + { + QString path = QFileDialog::getOpenFileName(this, tr("Select file"), _ui->source_database_lineEdit_path->text(), tr("Databases (*.db)")); + QFile file(path); + if(!path.isEmpty() && file.exists()) + { + _ui->source_comboBox_type->setCurrentIndex(3); + _ui->source_database_lineEdit_path->setText(path); + } + } + else + { + _ui->source_comboBox_type->setCurrentIndex(0); + } + + if(!fromPrefDialog && _obsoletePanels) + { + if(validateForm()) + { + this->writeSettings(); + } + else + { + this->readSettings(); + } + } +} + +void PreferencesDialog::setParameter(const std::string & key, const std::string & value) +{ + QObject * obj = _ui->stackedWidget->findChild(key.c_str()); + if(obj) + { + QSpinBox * spin = qobject_cast(obj); + QDoubleSpinBox * doubleSpin = qobject_cast(obj); + QComboBox * combo = qobject_cast(obj); + QCheckBox * check = qobject_cast(obj); + QLineEdit * lineEdit = qobject_cast(obj); + if(spin) + { + spin->setValue(QString(value.c_str()).toInt()); + } + else if(doubleSpin) + { + doubleSpin->setValue(QString(value.c_str()).toDouble()); + } + else if(combo) + { + combo->setCurrentIndex(QString(value.c_str()).toInt()); + } + else if(check) + { + check->setChecked(uStr2Bool(value.c_str())); + } + else if(lineEdit) + { + lineEdit->setText(value.c_str()); + } + else + { + ULOGGER_WARN("QObject called %s can't be cast to a supported widget", key.c_str()); + } + } + else + { + ULOGGER_WARN("Can't find the related QObject for parameter %s", key.c_str()); + } +} + +void PreferencesDialog::addParameter(int value) +{ + if(sender()) + { + this->addParameter(sender(), value); + } + else + { + ULOGGER_ERROR("This slot must be triggered by a signal, not a direct call..."); + } +} + +void PreferencesDialog::addParameter(double value) +{ + if(sender()) + { + this->addParameter(sender(), value); + } + else + { + ULOGGER_ERROR("This slot must be triggered by a signal, not a direct call..."); + } +} + +void PreferencesDialog::addParameter(const QString & value) +{ + if(sender()) + { + this->addParameter(sender(), value); + } + else + { + ULOGGER_ERROR("This slot must be triggered by a signal, not a direct call..."); + } +} + +// Booleans are interpreted like integers (see QCheckBox signals) +void PreferencesDialog::addParameter(const QObject * object, int value) +{ + if(object) + { + const QComboBox * comboBox = qobject_cast(object); + if(comboBox) + { + // Add related panels to parameters + if(comboBox == _ui->comboBox_vh_strategy) + { + if(value == 0) // 0 none + { + // No panel related... + } + else if(value == 1) // 1 epipolar + { + this->addParameters(_ui->groupBox_vh_epipolar); + } + } + else if(comboBox == _ui->comboBox_detector_strategy) + { + if(value == 0) // 0 surf + { + this->addParameters(_ui->groupBox_detector_surf); + } + else if(value == 1) // 1 star + { + this->addParameters(_ui->groupBox_detector_star); + } + else if(value == 2) // 2 sift + { + this->addParameters(_ui->groupBox_detector_sift); + } + } + else if(comboBox == _ui->comboBox_descriptor_strategy) + { + this->addParameters(_ui->groupBox_surfDescriptor); + } + } + // Make sure the value is inserted, check if the same key already exists + rtabmap::ParametersMap::iterator iter = _parameters.find(object->objectName().toStdString()); + if(iter != _parameters.end()) + { + _parameters.erase(iter); + } + _parameters.insert(rtabmap::ParametersPair(object->objectName().toStdString(), QString::number(value).toStdString())); + //ULOGGER_DEBUG("PreferencesDialog::addParameter(object, int) Added [\"%s\",\"%s\"]", object->objectName().toStdString().c_str(), QString::number(value).toStdString().c_str()); + } + else + { + ULOGGER_ERROR("Object is null"); + } +} + +void PreferencesDialog::addParameter(const QObject * object, double value) +{ + if(object) + { + // Make sure the value is inserted, check if the same key already exists + rtabmap::ParametersMap::iterator iter = _parameters.find(object->objectName().toStdString()); + if(iter != _parameters.end()) + { + _parameters.erase(iter); + } + _parameters.insert(rtabmap::ParametersPair(object->objectName().toStdString(), QString::number(value).toStdString())); + //ULOGGER_DEBUG("PreferencesDialog::addParameter(object, double) Added [\"%s\",\"%s\"]", object->objectName().toStdString().c_str(), QString::number(value).toStdString().c_str()); + } + else + { + ULOGGER_ERROR("Object is null"); + } +} + +void PreferencesDialog::addParameter(const QObject * object, const QString & value) +{ + if(object) + { + // Make sure the value is inserted, check if the same key already exists + rtabmap::ParametersMap::iterator iter = _parameters.find(object->objectName().toStdString()); + if(iter != _parameters.end()) + { + _parameters.erase(iter); + } + _parameters.insert(rtabmap::ParametersPair(object->objectName().toStdString(), value.toStdString())); + //ULOGGER_DEBUG("PreferencesDialog::addParameter(object, QString) Added [\"%s\",\"%s\"]", object->objectName().toStdString().c_str(), QString::number(value).toStdString().c_str()); + } + else + { + ULOGGER_ERROR("Object is null"); + } +} + +void PreferencesDialog::addParameters(const QGroupBox * box) +{ + if(box) + { + QObjectList children = box->children(); + //ULOGGER_DEBUG("PreferencesDialog::addParameters(QGroupBox) box %s has %d children", box->objectName().toStdString().c_str(), children.size()); + for(int i=0; i(children[i]); + const QDoubleSpinBox * doubleSpin = qobject_cast(children[i]); + const QComboBox * combo = qobject_cast(children[i]); + const QCheckBox * check = qobject_cast(children[i]); + const QLineEdit * lineEdit = qobject_cast(children[i]); + if(spin) + { + this->addParameter(spin, spin->value()); + } + else if(doubleSpin) + { + this->addParameter(doubleSpin, doubleSpin->value()); + } + else if(combo) + { + this->addParameter(combo, combo->currentIndex()); + } + else if(check) + { + this->addParameter(check, check->isChecked()); + } + else if(lineEdit) + { + this->addParameter(lineEdit, lineEdit->text()); + } + } + } +} + +void PreferencesDialog::makeObsoleteGeneralPanel() +{ + ULOGGER_DEBUG(""); + _obsoletePanels = _obsoletePanels | kPanelGeneral; +} + +void PreferencesDialog::makeObsoleteSourcePanel() +{ + ULOGGER_DEBUG(""); + _obsoletePanels = _obsoletePanels | kPanelSource; +} + +rtabmap::ParametersMap PreferencesDialog::getAllParameters() +{ + rtabmap::ParametersMap result; + rtabmap::ParametersMap tmpParameters = _parameters; + _parameters.clear(); + + QList boxes = this->getGroupBoxes(); + for(int i=0; iaddParameters(boxes.at(i)); + } + + result = _parameters; + _parameters = tmpParameters; + return result; +} + +QList PreferencesDialog::getGroupBoxes() +{ + QList boxes; + for(int i=0; i<_ui->stackedWidget->count(); ++i) + { + QGroupBox * gb = 0; + const QObjectList & children = _ui->stackedWidget->widget(i)->children(); + for(int j=0; j(children.at(j)))) + { + //ULOGGER_DEBUG("PreferencesDialog::setupTreeView() index(%d) Added %s, box name=%s", i, gb->title().toStdString().c_str(), gb->objectName().toStdString().c_str()); + break; + } + } + if(gb) + { + boxes.append(gb); + this->addParameters(gb); + } + else + { + ULOGGER_ERROR("A QGroupBox must be included in the first level of children in stacked widget, index=%d", i); + } + } + return boxes; +} + +void PreferencesDialog::setupPredictionPanel() +{ + QString prediction = _ui->lineEdit_bayes_predictionLC->text(); + QStringList values = prediction.split(' '); + if(values.size() < 2) + { + ULOGGER_ERROR("The prediction string is not valid (prediction=\"%s\",size=%d)", prediction.toStdString().c_str(), values.size()); + return; + } + + //Signals + connect(_ui->spinBox_bayes_neighbors, SIGNAL(valueChanged(int)), this, SLOT(updatePredictionLCSliders())); + + //Set values + _ui->spinBox_bayes_neighbors->setValue(values.size()-2); + this->updatePredictionLCSliders(); + if(_predictionLCSliders.size() != values.size()) + { + ULOGGER_ERROR("The sliders list were not updated"); + return; + } + for(int i=0; i<_predictionLCSliders.size(); ++i) + { + bool ok; + _predictionLCSliders.at(i)->setValue(int(QString(values.at(i)).toFloat(&ok) * 100)); + if(!ok) + { + ULOGGER_WARN("conversion failed to float with string \"%s\"", values.at(i).toStdString().c_str()); + } + } + _predictionPanelInitialized = true; + this->updatePredictionLC(); +} + +void PreferencesDialog::updatePredictionLCSliders() +{ + int neighborsCount = _ui->spinBox_bayes_neighbors->value(); + if(neighborsCount < 0 || neighborsCount>9) + { + ULOGGER_ERROR("neighborsCount not valid (=%d)", neighborsCount); + return; + } + + if(_predictionLCSliders.size() == 1) + { + _predictionLCSliders.clear(); // must be pair... + ULOGGER_WARN("The list of sliders is supposed to be pair"); + } + + if(_predictionLCSliders.size() == 0) + { + _predictionLCSliders.append(_ui->verticalSlider_prediction_vp); + _predictionLCSliders.append(_ui->verticalSlider_prediction_lp); + connect(_ui->verticalSlider_prediction_vp, SIGNAL(valueChanged(int)), this, SLOT(updatePredictionLC())); + connect(_ui->verticalSlider_prediction_lp, SIGNAL(valueChanged(int)), this, SLOT(updatePredictionLC())); + } + + // If we don't have enough sliders + while((_predictionLCSliders.size()-2) < neighborsCount) + { + int neighbor = _predictionLCSliders.size(); + QVBoxLayout * vLayout = 0; + QHBoxLayout * hLayout = 0; + QSlider * slider = 0; + QLabel * value = 0; + QLabel * title = 0; + + slider = new QSlider(Qt::Vertical, this); + + title = new QLabel(QString("l%1").arg(neighbor-1), slider); + + title->setAlignment(Qt::AlignCenter); + value = new QLabel(slider); + value->setAlignment(Qt::AlignCenter); + //layout + vLayout = new QVBoxLayout(); + vLayout->addWidget(title); + hLayout = new QHBoxLayout(); + hLayout->addWidget(slider); + vLayout->addLayout(hLayout); + vLayout->addWidget(value); + + _ui->horizontalLayout_prior_NP->insertLayout(-1, vLayout); + + _predictionLCSliders.push_back(slider); + connect(slider, SIGNAL(valueChanged(int)), this, SLOT(updatePredictionLC())); + connect(slider, SIGNAL(valueChanged(int)), value, SLOT(setNum(int))); + } + + // If we have too much sliders + while((_predictionLCSliders.size()-2) > neighborsCount) + { + // delete layouts and items + delete _ui->horizontalLayout_prior_NP->itemAt(_ui->horizontalLayout_prior_NP->count()-1)->layout()->takeAt(0)->widget(); //label + delete _ui->horizontalLayout_prior_NP->itemAt(_ui->horizontalLayout_prior_NP->count()-1)->layout()->itemAt(0)->layout()->takeAt(0)->widget(); //slider + delete _ui->horizontalLayout_prior_NP->itemAt(_ui->horizontalLayout_prior_NP->count()-1)->layout()->takeAt(0)->layout(); //slider hlayout + delete _ui->horizontalLayout_prior_NP->itemAt(_ui->horizontalLayout_prior_NP->count()-1)->layout()->takeAt(0)->widget(); //spinbox + delete _ui->horizontalLayout_prior_NP->takeAt(_ui->horizontalLayout_prior_NP->count()-1); + // remove the last slider + _predictionLCSliders.removeLast(); + } + + this->updatePredictionLC(); +} + +void PreferencesDialog::updatePredictionLC() +{ + if(!_predictionPanelInitialized) + { + return; + } + if(_predictionLCSliders.size() < 2) + { + ULOGGER_ERROR("Not enough slider in the list (%d)", _predictionLCSliders.size()); + return; + } + + QString values; + QVector dataX((_predictionLCSliders.size()-2)*2 + 1); + QVector dataY((_predictionLCSliders.size()-2)*2 + 1); + float value; + float sum = 0; + int lvl = 1; + int loopClosureIndex = (dataX.size()-1)/2; + for(int i=0; i<_predictionLCSliders.size(); ++i) + { + value = (float(_predictionLCSliders.at(i)->value()))/100; + sum+=value; + if(i==0) + { + //do nothing + } + else if(i == 1) + { + dataY[loopClosureIndex] = value; + dataX[loopClosureIndex] = 0; + } + else + { + dataY[loopClosureIndex-lvl] = value; + dataX[loopClosureIndex-lvl] = -lvl; + dataY[loopClosureIndex+lvl] = value; + dataX[loopClosureIndex+lvl] = lvl; + ++lvl; + sum+=value; // double sum + } + values.append(QString::number(value)); + if(i+1 < _predictionLCSliders.size()) + { + values.append(' '); + } + } + + _ui->label_prediction_sum->setNum(sum); + if(sum<=0 || sum>1.001) + { + _ui->label_prediction_sum->setText(QString("") + _ui->label_prediction_sum->text() + ""); + ULOGGER_WARN("The prediction is not valid (the sum must be between >0 && <=1) (sum=%f)", sum); + } + else if(sum == 1) + { + _ui->label_prediction_sum->setText(QString("") + _ui->label_prediction_sum->text() + ""); + } + else + { + _ui->label_prediction_sum->setText(QString("") + _ui->label_prediction_sum->text() + ""); + } + + _ui->predictionPlot->removeCurves(); + _ui->predictionPlot->addCurve(new PlotCurve("Prediction", dataX, dataY, _ui->predictionPlot)); + _ui->lineEdit_bayes_predictionLC->setText(values); +} + +void PreferencesDialog::setupKpRoiPanel() +{ + QStringList strings = _ui->lineEdit_kp_roi->text().split(' '); + if(strings.size()!=4) + { + UERROR("ROI must have 4 values (%s)", _ui->lineEdit_kp_roi->text().toStdString().c_str()); + return; + } + _ui->doubleSpinBox_kp_roi0->setValue(strings[0].toDouble()*100.0); + _ui->doubleSpinBox_kp_roi1->setValue(strings[1].toDouble()*100.0); + _ui->doubleSpinBox_kp_roi2->setValue(strings[2].toDouble()*100.0); + _ui->doubleSpinBox_kp_roi3->setValue(strings[3].toDouble()*100.0); +} + +void PreferencesDialog::updateKpROI() +{ + QStringList strings; + strings.append(QString::number(_ui->doubleSpinBox_kp_roi0->value()/100.0)); + strings.append(QString::number(_ui->doubleSpinBox_kp_roi1->value()/100.0)); + strings.append(QString::number(_ui->doubleSpinBox_kp_roi2->value()/100.0)); + strings.append(QString::number(_ui->doubleSpinBox_kp_roi3->value()/100.0)); + _ui->lineEdit_kp_roi->setText(strings.join(" ")); +} + +void PreferencesDialog::changeWorkingDirectory() +{ + QString directory = QFileDialog::getExistingDirectory(this, tr("Working directory"), _ui->lineEdit_workingDirectory->text()); + if(!directory.isEmpty()) + { + ULOGGER_DEBUG("New working directory = %s", directory.toStdString().c_str()); + _ui->lineEdit_workingDirectory->setText(directory); + } +} + +void PreferencesDialog::changeDictionaryPath() +{ + QString path; + if(_ui->lineEdit_dictionaryPath->text().isEmpty()) + { + path = QFileDialog::getOpenFileName(this, tr("Dictionary"), this->getWorkingDirectory()); + } + else + { + path = QFileDialog::getOpenFileName(this, tr("Dictionary"), _ui->lineEdit_dictionaryPath->text()); + } + if(!path.isEmpty()) + { + _ui->lineEdit_dictionaryPath->setText(path); + } +} + +/*** GETTERS ***/ +//General +int PreferencesDialog::getGeneralLoggerLevel() const +{ + return _ui->comboBox_loggerLevel->currentIndex(); +} +int PreferencesDialog::getGeneralLoggerEventLevel() const +{ + return _ui->comboBox_loggerEventLevel->currentIndex(); +} +int PreferencesDialog::getGeneralLoggerPauseLevel() const +{ + return _ui->comboBox_loggerPauseLevel->currentIndex(); +} +int PreferencesDialog::getGeneralLoggerType() const +{ + return _ui->comboBox_loggerType->currentIndex(); +} +bool PreferencesDialog::getGeneralLoggerPrintTime() const +{ + return _ui->checkBox_logger_printTime->isChecked(); +} +bool PreferencesDialog::isVerticalLayoutUsed() const +{ + return _ui->checkBox_verticalLayoutUsed->isChecked(); +} +bool PreferencesDialog::isImageFlipped() const +{ + return _ui->checkBox_imageFlipped->isChecked(); +} +bool PreferencesDialog::imageRejectedShown() const +{ + return _ui->checkBox_imageRejectedShown->isChecked(); +} +bool PreferencesDialog::beepOnPause() const +{ + return _ui->checkBox_beep->isChecked(); +} +int PreferencesDialog::getKeypointsOpacity() const +{ + return _ui->horizontalSlider_keypointsOpacity->value(); +} + +// Source +double PreferencesDialog::getGeneralImageRate() const +{ + return _ui->general_doubleSpinBox_imgRate->value(); +} +bool PreferencesDialog::getGeneralAutoRestart() const +{ + return _ui->general_checkBox_autoRestart->isChecked(); +} +int PreferencesDialog::getSourceType() const +{ + return _ui->source_comboBox_type->currentIndex(); +} +QString PreferencesDialog::getSourceTypeStr() const +{ + return _ui->source_comboBox_type->currentText(); +} +int PreferencesDialog::getSourceWidth() const +{ + return _ui->source_spinBox_imgWidth->value(); +} +int PreferencesDialog::getSourceHeight() const +{ + return _ui->source_spinBox_imgheight->value(); +} +QString PreferencesDialog::getSourceImagesPath() const +{ + return _ui->source_images_lineEdit_path->text(); +} +int PreferencesDialog::getSourceImagesStartPos() const +{ + return _ui->source_images_spinBox_startPos->value(); +} +bool PreferencesDialog::getSourceImagesRefreshDir() const +{ + return _ui->source_images_refreshDir->isChecked(); +} +QString PreferencesDialog::getSourceVideoPath() const +{ + return _ui->source_video_lineEdit_path->text(); +} +int PreferencesDialog::getSourceUsbDeviceId() const +{ + return _ui->source_usbDevice_spinBox_id->value(); +} +QString PreferencesDialog::getSourceDatabasePath() const +{ + return _ui->source_database_lineEdit_path->text(); +} +bool PreferencesDialog::getSourceDatabaseIgnoreChildren() const +{ + return _ui->source_database_checkBox_ignoreChildren->isChecked(); +} + +double PreferencesDialog::getLoopThr() const +{ + return _ui->general_doubleSpinBox_hardThr->value(); +} +double PreferencesDialog::getReacThr() const +{ + return _ui->general_doubleSpinBox_reactivationThr->value(); +} +double PreferencesDialog::getVpThr() const +{ + return _ui->general_doubleSpinBox_vp->value(); +} + +bool PreferencesDialog::isImagesKept() const +{ + return _ui->general_checkBox_imagesKept->isChecked(); +} +double PreferencesDialog::getTimeLimit() const +{ + return _ui->general_doubleSpinBox_timeThr->value(); +} + +/*** SETTERS ***/ +void PreferencesDialog::setHardThr(int value) +{ + double dValue = double(value)/100; + ULOGGER_DEBUG("PreferencesDialog::setHardThr(%f)", dValue); + if(_ui->general_doubleSpinBox_hardThr->value() != dValue) + { + _ui->general_doubleSpinBox_hardThr->setValue(dValue); + if(validateForm()) + { + this->writeSettings(); + } + else + { + this->readSettings(); + } + } +} + +void PreferencesDialog::setReactivationThr(int value) +{ + double dValue = double(value)/100; + ULOGGER_DEBUG("reactivation thr=%f", dValue); + if(_ui->general_doubleSpinBox_reactivationThr->value() != dValue) + { + _ui->general_doubleSpinBox_reactivationThr->setValue(dValue); + if(validateForm()) + { + this->writeSettings(); + } + else + { + this->readSettings(); + } + } +} + +void PreferencesDialog::setImgRate(double value) +{ + ULOGGER_DEBUG("imgRate=%2.2f", value); + if(_ui->general_doubleSpinBox_imgRate->value() != value) + { + _ui->general_doubleSpinBox_imgRate->setValue(value); + if(validateForm()) + { + this->writeSettings(); + } + else + { + this->readSettings(); + } + } +} + +void PreferencesDialog::setAutoRestart(bool value) +{ + ULOGGER_DEBUG("autoRestart=%d", value); + if(_ui->general_checkBox_autoRestart->isChecked() != value) + { + _ui->general_checkBox_autoRestart->setChecked(value); + if(validateForm()) + { + this->writeSettings(); + } + else + { + this->readSettings(); + } + } +} + +void PreferencesDialog::setTimeLimit(double value) +{ + ULOGGER_DEBUG("timeLimit=%fs", value); + if(_ui->general_doubleSpinBox_timeThr->value() != value) + { + _ui->general_doubleSpinBox_timeThr->setValue(value); + if(validateForm()) + { + this->writeSettings(); + } + else + { + this->readSettings(); + } + } +} + +} diff --git a/guilib/src/StatsToolBox.cpp b/guilib/src/StatsToolBox.cpp new file mode 100644 index 0000000000..af62ee107a --- /dev/null +++ b/guilib/src/StatsToolBox.cpp @@ -0,0 +1,435 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "StatsToolBox.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Plot.h" +#include "utilite/ULogger.h" + +namespace rtabmap { + +StatItem::StatItem(const QString & name, float x, float y, const QString & unit, const QMenu * menu, QGridLayout * grid, QWidget * parent) : + QWidget(parent), + _xValue(x) +{ + this->setupUi(grid); + _name->setText(name); + _value->setNum(y); + _unit->setText(unit); + this->updateMenu(menu); +} + +StatItem::~StatItem() +{ + +} + +void StatItem::setValue(float x, float y) +{ + _value->setText(QString::number(y, 'g', 4)); + _xValue = x; + emit valueChanged(x,y); +} + +void StatItem::setupUi(QGridLayout * grid) +{ + _menu = new QMenu(this); + _menu->addMenu("Add to figure..."); + _button = new QToolButton(this); + _button->setIcon(QIcon(":/images/Plot16.png")); + _button->setPopupMode(QToolButton::InstantPopup); + _button->setMenu(_menu); + _name = new QLabel(this); + _name->setWordWrap(true); + _value = new QLabel(this); + _unit = new QLabel(this); + + if(grid) + { + int row = grid->rowCount(); + + //This fixes an issue where the + //button (used on col 0) of the first line in the + //toolbox couldn't be clicked + grid->addWidget(_button, row, 3); + grid->addWidget(_name, row, 0); + grid->addWidget(_value, row, 1); + grid->addWidget(_unit, row, 2); + } + else + { + QHBoxLayout * layout = new QHBoxLayout(this); + this->setLayout(layout); + layout->addWidget(_button); + layout->addWidget(_name); + layout->addWidget(_value); + layout->addWidget(_unit); + layout->addStretch(); + layout->setMargin(0); + } +} + +void StatItem::updateMenu(const QMenu * menu) +{ + _menu->clear(); + QAction * action; + QList actions = menu->actions(); + QMenu * plotMenu = _menu->addMenu("Add to figure..."); + for(int i=0; iaddAction(actions.at(i)->text()); + connect(action, SIGNAL(triggered()), this, SLOT(preparePlotRequest())); + } +} + +void StatItem::preparePlotRequest() +{ + QAction * action = qobject_cast(sender()); + if(action) + { + emit plotRequested(this, action->text()); + } +} + + + + + + + + +StatsToolBox::StatsToolBox(QWidget * parent) : + QWidget(parent) +{ + ULOGGER_DEBUG(""); + //Statistics in the GUI (for plotting) + _statBox = new QToolBox(this); + this->setLayout(new QVBoxLayout()); + this->layout()->setMargin(0); + this->layout()->addWidget(_statBox); + _statBox->layout()->setSpacing(0); + _plotMenu = new QMenu(this); + _plotMenu->addAction(tr("")); + _workingDirectory = QDir::homePath(); +} + +StatsToolBox::~StatsToolBox() +{ + closeFigures(); +} + +void StatsToolBox::closeFigures() +{ + QMap figuresTmp = _figures; + for(QMap::iterator iter = figuresTmp.begin(); iter!=figuresTmp.end(); ++iter) + { + iter.value()->close(); + } +} + +void StatsToolBox::updateStat(const QString & statFullName, float x, float y) +{ + // round float to max 2 numbers after the dot + //x = (float(int(100*x)))/100; + //y = (float(int(100*y)))/100; + + StatItem * item = _statBox->findChild(statFullName); + if(item) + { + item->setValue(x, y); + } + else + { + // statFullName format : "Grp/Name/unit" + QStringList list = statFullName.split('/'); + QString grp; + QString name; + QString unit; + if(list.size() >= 3) + { + grp = list.at(0); + name = list.at(1); + unit = list.at(2); + } + else if(list.size() == 2) + { + grp = list.at(0); + name = list.at(1); + } + else if(list.size() == 1) + { + name = list.at(0); + } + else + { + ULOGGER_WARN("A statistic has no name"); + return; + } + + if(grp.isEmpty()) + { + grp = tr("Global"); + } + + int index = -1; + for(int i=0; i<_statBox->count(); ++i) + { + if(_statBox->itemText(i).compare(grp) == 0) + { + index = i; + break; + } + } + + if(index<0) + { + QWidget * newWidget = new QWidget(_statBox); + index = _statBox->addItem(newWidget, grp); + QVBoxLayout * layout = new QVBoxLayout(newWidget); + newWidget->setLayout(layout); + QGridLayout * grid = new QGridLayout(); + grid->setVerticalSpacing(2); + grid->setColumnStretch(0, 1); + layout->addLayout(grid); + layout->addStretch(); + } + + QVBoxLayout * layout = qobject_cast(_statBox->widget(index)->layout()); + if(!layout) + { + ULOGGER_ERROR("Layout is null ?!?"); + return; + } + QGridLayout * grid = qobject_cast(layout->itemAt(0)->layout()); + if(!grid) + { + ULOGGER_ERROR("Layout is null ?!?"); + return; + } + + item = new StatItem(name, x, y, unit, _plotMenu, grid, _statBox->widget(index)); + item->setObjectName(statFullName); + + //layout->insertWidget(layout->count()-1, item); + connect(item, SIGNAL(plotRequested(const StatItem *, const QString &)), this, SLOT(plot(const StatItem *, const QString &))); + connect(this, SIGNAL(menuChanged(const QMenu *)), item, SLOT(updateMenu(const QMenu *))); + } +} + +void StatsToolBox::plot(const StatItem * stat, const QString & plotName) +{ + QWidget * fig = _figures.value(plotName, (QWidget*)0); + Plot * plot = 0; + if(fig) + { + plot = fig->findChild(plotName); + } + if(plot) + { + // if not already in the plot + if(!plot->contains(stat->objectName())) + { + PlotCurve * curve = new PlotCurve(stat->objectName(), plot); + curve->setPen(plot->getRandomPenColored()); + connect(stat, SIGNAL(valueChanged(float, float)), curve, SLOT(addValue(float, float))); + if(!plot->addCurve(curve)) + { + ULOGGER_WARN("Already added to the figure"); + } + } + else + { + ULOGGER_WARN("Already added to the figure"); + } + plot->activateWindow(); + } + else + { + //Create a new plot + QString id = tr("Figure 0"); + if(_plotMenu->actions().size()) + { + id = _plotMenu->actions().last()->text(); + } + id.replace(tr("Figure "), ""); + QString newPlotName = QString(tr("Figure %1")).arg(id.toInt()+1); + //Dock + QWidget * figure = new QWidget(0, Qt::Window); + _figures.insert(newPlotName, figure); + figure->setLayout(new QHBoxLayout()); + figure->setWindowTitle(newPlotName); + figure->setAttribute(Qt::WA_DeleteOnClose, true); + connect(figure, SIGNAL(destroyed(QObject*)), this, SLOT(figureDeleted(QObject*))); + //Plot + Plot * newPlot = new Plot(figure); + newPlot->setWorkingDirectory(_workingDirectory); + newPlot->setMaxVisibleItems(10); + newPlot->setObjectName(newPlotName); + figure->layout()->addWidget(newPlot); + _plotMenu->addAction(newPlotName); + + //Add a new curve linked to the statBox + PlotCurve * curve = new PlotCurve(stat->objectName(), newPlot); + curve->setPen(newPlot->getRandomPenColored()); + connect(stat, SIGNAL(valueChanged(float, float)), curve, SLOT(addValue(float, float))); + if(!newPlot->addCurve(curve)) + { + ULOGGER_ERROR("Not supposed to be here !?!"); + delete curve; + } + figure->show(); + + emit menuChanged(_plotMenu); + } +} + +void StatsToolBox::figureDeleted(QObject * obj) +{ + if(obj) + { + QWidget * plot = qobject_cast(obj); + if(plot) + { + _figures.remove(plot->windowTitle()); + QList actions = _plotMenu->actions(); + for(int i=0; itext().compare(plot->windowTitle()) == 0) + { + _plotMenu->removeAction(actions.at(i)); + delete actions[i]; + emit menuChanged(_plotMenu); + break; + } + } + } + else + { + UERROR(""); + } + } + else + { + UERROR(""); + } +} + +void StatsToolBox::contextMenuEvent(QContextMenuEvent * event) +{ + QMenu topMenu(this); + QMenu * menu = topMenu.addMenu(tr("Add all statistics from tab \"%1\" to...").arg(_statBox->itemText(_statBox->currentIndex()))); + QList actions = _plotMenu->actions(); + menu->addActions(actions); + QAction * action = topMenu.exec(event->globalPos()); + QString plotName; + if(action) + { + for(int i=0; itext(); + break; + } + } + } + + if(!plotName.isEmpty()) + { + QList items = _statBox->currentWidget()->findChildren(); + for(int i=0; iplot(items.at(i), plotName); + if(plotName.compare(tr("")) == 0) + { + plotName = _plotMenu->actions().last()->text(); + } + } + } +} + +void StatsToolBox::getFiguresSetup(QList & curvesPerFigure, QStringList & curveNames) +{ + curvesPerFigure.clear(); + curveNames.clear(); + for(QMap::iterator i=_figures.begin(); i!=_figures.end(); ++i) + { + QList plots = i.value()->findChildren(); + if(plots.size() == 1) + { + QStringList names = plots[0]->curveNames(); + curvesPerFigure.append(names.size()); + curveNames.append(names); + } + else + { + UERROR(""); + } + } +} +void StatsToolBox::addCurve(const QString & name, bool newFigure) +{ + StatItem * item = _statBox->findChild(name); + if(!item) + { + this->updateStat(name, 0, 0); + item = _statBox->findChild(name); + } + + if(item) + { + if(newFigure) + { + this->plot(item, ""); + } + else + { + this->plot(item, _plotMenu->actions().last()->text()); + } + } + else + { + ULOGGER_ERROR("Not supposed to be here..."); + } +} + +void StatsToolBox::setWorkingDirectory(const QString & workingDirectory) +{ + if(QDir(_workingDirectory).exists()) + { + _workingDirectory = workingDirectory; + } + else + { + ULOGGER_ERROR("The directory \"%s\" doesn't exist", workingDirectory.toStdString().c_str()); + } +} + +} diff --git a/guilib/src/StatsToolBox.h b/guilib/src/StatsToolBox.h new file mode 100644 index 0000000000..dfaef450b1 --- /dev/null +++ b/guilib/src/StatsToolBox.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef STATSTOOLBOX_H_ +#define STATSTOOLBOX_H_ + +#include +#include + +class QToolButton; +class QLabel; +class QMenu; +class QGridLayout; +class QToolBox; + +namespace rtabmap { + +class StatItem : public QWidget +{ + Q_OBJECT; + +public: + StatItem(const QString & name, float x, float y, const QString & unit = QString(), const QMenu * menu = 0, QGridLayout * grid = 0, QWidget * parent = 0); + virtual ~StatItem(); + void setValue(float x, float y); + +public slots: + void updateMenu(const QMenu * menu); + +signals: + void valueChanged(float, float); + void plotRequested(const StatItem *, const QString &); + +private slots: + void preparePlotRequest(); + +private: + void setupUi(QGridLayout * grid); + +private: + QToolButton * _button; + QLabel * _name; + QLabel * _value; + QLabel * _unit; + QMenu * _menu; + float _xValue; +}; + + + + +class StatsToolBox : public QWidget +{ + Q_OBJECT; + +public: + StatsToolBox(QWidget * parent); + virtual ~StatsToolBox(); + + void getFiguresSetup(QList & curvesPerFigure, QStringList & curveNames); + void addCurve(const QString & name, bool newFigure = true); + void setWorkingDirectory(const QString & workingDirectory); + void closeFigures(); + +public slots: + void updateStat(const QString & statFullName, float x, float y); + +signals: + void menuChanged(const QMenu *); + +private slots: + void plot(const StatItem * stat, const QString & plotName = QString()); + void figureDeleted(QObject * obj); + +protected: + virtual void contextMenuEvent(QContextMenuEvent * event); + +private: + QMenu * _plotMenu; + QToolBox * _statBox; + QString _workingDirectory; + QMap _figures; +}; + +} + +#endif /* STATSTOOLBOX_H_ */ diff --git a/guilib/src/images/IntRoLab.png b/guilib/src/images/IntRoLab.png new file mode 100644 index 0000000000..12ed4e411b Binary files /dev/null and b/guilib/src/images/IntRoLab.png differ diff --git a/guilib/src/images/IntRoLabSmall.png b/guilib/src/images/IntRoLabSmall.png new file mode 100644 index 0000000000..869fedea31 Binary files /dev/null and b/guilib/src/images/IntRoLabSmall.png differ diff --git a/guilib/src/images/Pause.ico b/guilib/src/images/Pause.ico new file mode 100644 index 0000000000..da2bf3419b Binary files /dev/null and b/guilib/src/images/Pause.ico differ diff --git a/guilib/src/images/PauseLoopRejected.ico b/guilib/src/images/PauseLoopRejected.ico new file mode 100644 index 0000000000..360d3f3b75 Binary files /dev/null and b/guilib/src/images/PauseLoopRejected.ico differ diff --git a/guilib/src/images/PauseNormal.png b/guilib/src/images/PauseNormal.png new file mode 100644 index 0000000000..b043b640b3 Binary files /dev/null and b/guilib/src/images/PauseNormal.png differ diff --git a/guilib/src/images/PauseNormalRed.png b/guilib/src/images/PauseNormalRed.png new file mode 100644 index 0000000000..39a245b862 Binary files /dev/null and b/guilib/src/images/PauseNormalRed.png differ diff --git a/guilib/src/images/PauseOnLoop.ico b/guilib/src/images/PauseOnLoop.ico new file mode 100644 index 0000000000..c8ba2b207a Binary files /dev/null and b/guilib/src/images/PauseOnLoop.ico differ diff --git a/guilib/src/images/Play1Normal.png b/guilib/src/images/Play1Normal.png new file mode 100644 index 0000000000..bfa64fe28b Binary files /dev/null and b/guilib/src/images/Play1Normal.png differ diff --git a/guilib/src/images/Plot.ico b/guilib/src/images/Plot.ico new file mode 100644 index 0000000000..3e129775c8 Binary files /dev/null and b/guilib/src/images/Plot.ico differ diff --git a/guilib/src/images/Plot16.png b/guilib/src/images/Plot16.png new file mode 100644 index 0000000000..91432b135d Binary files /dev/null and b/guilib/src/images/Plot16.png differ diff --git a/guilib/src/images/Plot48.png b/guilib/src/images/Plot48.png new file mode 100644 index 0000000000..fa475cb6f1 Binary files /dev/null and b/guilib/src/images/Plot48.png differ diff --git a/guilib/src/images/RTAB-Map.ico b/guilib/src/images/RTAB-Map.ico new file mode 100644 index 0000000000..eb277e91a4 Binary files /dev/null and b/guilib/src/images/RTAB-Map.ico differ diff --git a/guilib/src/images/RTAB-Map.png b/guilib/src/images/RTAB-Map.png new file mode 100644 index 0000000000..181cbb4e06 Binary files /dev/null and b/guilib/src/images/RTAB-Map.png differ diff --git a/guilib/src/images/Stop1Normal.png b/guilib/src/images/Stop1Normal.png new file mode 100644 index 0000000000..a28d9f430c Binary files /dev/null and b/guilib/src/images/Stop1Normal.png differ diff --git a/guilib/src/images/Stop1NormalYellow.png b/guilib/src/images/Stop1NormalYellow.png new file mode 100644 index 0000000000..743603d9ee Binary files /dev/null and b/guilib/src/images/Stop1NormalYellow.png differ diff --git a/guilib/src/images/crosshatch_metal_grille_9280154_150.JPG b/guilib/src/images/crosshatch_metal_grille_9280154_150.JPG new file mode 100644 index 0000000000..72f52a7ad9 Binary files /dev/null and b/guilib/src/images/crosshatch_metal_grille_9280154_150.JPG differ diff --git a/guilib/src/images/metal_7280826_512.jpg b/guilib/src/images/metal_7280826_512.jpg new file mode 100644 index 0000000000..7aff2193d2 Binary files /dev/null and b/guilib/src/images/metal_7280826_512.jpg differ diff --git a/guilib/src/qss/default.qss b/guilib/src/qss/default.qss new file mode 100644 index 0000000000..d289c313e5 --- /dev/null +++ b/guilib/src/qss/default.qss @@ -0,0 +1,17 @@ + +/*QGroupBox { + background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #E0E0E0, stop: 1 #FFFFFF); + border: 2px solid gray; + border-radius: 5px; + margin-top: 1ex; /* leave space at the top for the title */ + } + + QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top center; + padding: 0 3px; + background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #FFOECE, stop: 1 #FFFFFF); + } + */ \ No newline at end of file diff --git a/guilib/src/qtipl.cpp b/guilib/src/qtipl.cpp new file mode 100644 index 0000000000..b871efb0c9 --- /dev/null +++ b/guilib/src/qtipl.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#include "qtipl.h" +#include "utilite/ULogger.h" +#include + +namespace rtabmap { + +// TODO : support only from gray 8bits ? +QImage Ipl2QImage(const IplImage *newImage) +{ + QImage qtemp; + if (newImage && newImage->depth == IPL_DEPTH_8U && cvGetSize(newImage).width>0) + { + int x; + int y; + char* data = newImage->imageData; + + qtemp= QImage(newImage->width, newImage->height,QImage::Format_RGB32 ); + for( y = 0; y < newImage->height; y++, data +=newImage->widthStep ) + { + for( x = 0; x < newImage->width; x++) + { + uint *p = (uint*)qtemp.scanLine (y) + x; + *p = qRgb(data[x * newImage->nChannels+2], data[x * newImage->nChannels+1],data[x * newImage->nChannels]); + } + } + } + else + { + ULOGGER_ERROR("Wrong IplImage format"); + } + return qtemp; +} + +} diff --git a/guilib/src/qtipl.h b/guilib/src/qtipl.h new file mode 100644 index 0000000000..47e008a5e8 --- /dev/null +++ b/guilib/src/qtipl.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke + * + * This file is part of RTAB-Map. + * + * RTAB-Map is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RTAB-Map is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RTAB-Map. If not, see . + */ + +#ifndef QTIPL_H +#define QTIPL_H + +#include +#include + +namespace rtabmap { + +QImage Ipl2QImage(const IplImage *newImage); + +} + +#endif diff --git a/guilib/src/resources/PreferencesModel.txt b/guilib/src/resources/PreferencesModel.txt new file mode 100644 index 0000000000..47e572855e --- /dev/null +++ b/guilib/src/resources/PreferencesModel.txt @@ -0,0 +1,22 @@ +0General +1Bayes filter +1Similarity +1Memory strategy +0Source +1Images +1Video +1Usb device +0Signature type +1Keypoint-based +2Detectors +3SURF detector +3Star detector +2Descriptor +2Dictionary +1FFT-based +0Hypotheses creation +1Simple +1Std dev +0Hypotheses verification +1Sequence +1Epipolar constraints diff --git a/guilib/src/ui/aboutDialog.ui b/guilib/src/ui/aboutDialog.ui new file mode 100644 index 0000000000..d6a1cf3da9 --- /dev/null +++ b/guilib/src/ui/aboutDialog.ui @@ -0,0 +1,252 @@ + + + aboutDialog + + + + 0 + 0 + 543 + 368 + + + + + 0 + 0 + + + + About RTAB-Map + + + + + + + + + 100 + 100 + + + + + + + :/images/RTAB-Map.png + + + true + + + Qt::AlignCenter + + + + + + + QFrame::Box + + + QFrame::Raised + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Lucida Grande'; font-size:13pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:14pt; font-weight:600;">RTAB-Map : Real-Time Appearance-Based Mapping</span></p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + RTAB-Map is an appearance-based loop closure detector for autonomous robot. This interface is used to control or to monitor the core process. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + true + + + + + + + + + Author : + + + + + + + Links : + + + + + + + Version : + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Lucida Grande'; font-size:13pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://www.code.google.com/p/rtabmap"><span style=" text-decoration: underline; color:#0000ff;">Source page</span></a></p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + true + + + + + + + Mathieu Labbé, matlabbe@gmail.com + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Lucida Grande'; font-size:13pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://introlab.gel.usherbrooke.ca/mediawiki-introlab/index.php/RTAB-Map"><span style=" text-decoration: underline; color:#0000ff;">Home page</span></a></p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + true + + + + + + + + + Qt::Vertical + + + + 20 + 1 + + + + + + + + + + + :/images/IntRoLabSmall.png + + + Qt::AlignCenter + + + + + + + Copyright (C) 2011 IntRoLab - Université de Sherbrooke + + + Qt::AlignCenter + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + + + buttonBox + accepted() + aboutDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + aboutDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/guilib/src/ui/consoleWidget.ui b/guilib/src/ui/consoleWidget.ui new file mode 100644 index 0000000000..8450f179a3 --- /dev/null +++ b/guilib/src/ui/consoleWidget.ui @@ -0,0 +1,34 @@ + + + consoleWidget + + + + 0 + 0 + 400 + 300 + + + + consoleWidget + + + + 0 + + + + + QTextEdit::NoWrap + + + true + + + + + + + + diff --git a/guilib/src/ui/mainWindow.ui b/guilib/src/ui/mainWindow.ui new file mode 100644 index 0000000000..e8060c8826 --- /dev/null +++ b/guilib/src/ui/mainWindow.ui @@ -0,0 +1,877 @@ + + + mainWindow + + + + 0 + 0 + 612 + 632 + + + + RTAB-Map : Real-Time Appearance-Based Mapping + + + + :/images/RTAB-Map.ico:/images/RTAB-Map.ico + + + QMainWindow::AllowNestedDocks|QMainWindow::AllowTabbedDocks|QMainWindow::AnimatedDocks|QMainWindow::VerticalTabs + + + + + 0 + + + 0 + + + + + QFrame::Box + + + QFrame::Raised + + + + 0 + + + 0 + + + + + + + -Source- + + + Qt::AlignCenter + + + + + + + + + + + + 0 + 18 + + + + + + + Qt::AlignCenter + + + + + + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + + + + + + + Show features + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + Show image + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + QFrame::Box + + + QFrame::Raised + + + + 0 + + + 0 + + + + + + + -Loop closure detection- + + + Qt::AlignCenter + + + + + + + + + + + + 0 + 18 + + + + + + + Qt::AlignCenter + + + + + + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + + + + + + + Show features + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + Show image + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + 0 + 0 + 612 + 22 + + + + + File + + + + + + Edit + + + + + + + + + + + + + + + + + ? + + + + + + + + Detection + + + + Select source + + + + + + + + + + + + + + + + + + Window + + + + Show view + + + + + + Figures + + + + + + + + + + + + + + + + + + + QDockWidget::AllDockWidgetFeatures + + + A posteriori PDF + + + 8 + + + + + + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + + + + + + true + + + Statistics + + + 8 + + + + + 2 + + + + + QLayout::SetMinimumSize + + + 2 + + + + + Source + + + + + + + Unknown + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 1 + + + 100.000000000000000 + + + 0.100000000000000 + + + 2.000000000000000 + + + + + + + Elapsed time (hh:mm:ss) + + + true + + + + + + + Unknown + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Current image id + + + + + + + Unknown + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Loop closures detected + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Loop closures detected +(only reactivated ones) + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Loop closures rejected + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Image rate + + + + + + + Hz + + + + + + + Time limit processing + + + true + + + + + + + s + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 2 + + + 10.000000000000000 + + + 0.450000000000000 + + + + + + + + + QFrame::Box + + + QFrame::Raised + + + + 0 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + true + + + Likelihood + + + 8 + + + + + + + + 20 + 20 + + + + + + + + + + Console + + + 1 + + + + + + + + + + + + Exit + + + + + true + + + Usb device + + + + + true + + + Video... + + + + + true + + + Images... + + + + + SURF + + + + + Fourier + + + + + SIFT + + + + + CenSurE + + + + + false + + + Help + + + + + About... + + + + + Preferences... + + + + + + :/images/Play1Normal.png:/images/Play1Normal.png + + + Start + + + + + true + + + + :/images/PauseNormalRed.png:/images/PauseNormalRed.png + + + Pause + + + + + true + + + + :/images/PauseOnLoop.ico:/images/PauseOnLoop.ico + + + Pause on match + + + + + + :/images/Stop1NormalYellow.png:/images/Stop1NormalYellow.png + + + Stop + + + + + Save/reload the memory + + + + + Apply settings to the detector + + + + + Dump the memory + + + + + true + + + Database... + + + + + true + + + + :/images/PauseLoopRejected.ico:/images/PauseLoopRejected.ico + + + Pause when a loop hypothesis is rejected + + + + + Clear the cache + + + + + Save state + + + + + Load state + + + + + true + + + Auto screen capture + + + + + Dump the prediction matrix + + + + + Generate map... + + + + + Delete memory + + + + + Show working directory in file browser + + + + + + rtabmap::Plot + QWidget +
Plot.h
+ 1 +
+ + rtabmap::ImageView + QGraphicsView +
ImageView.h
+
+ + rtabmap::StatsToolBox + QWidget +
StatsToolBox.h
+ 1 +
+ + rtabmap::ConsoleWidget + QWidget +
ConsoleWidget.h
+ 1 +
+
+ + + + +
diff --git a/guilib/src/ui/preferencesDialog.ui b/guilib/src/ui/preferencesDialog.ui new file mode 100644 index 0000000000..96a8cb6707 --- /dev/null +++ b/guilib/src/ui/preferencesDialog.ui @@ -0,0 +1,2877 @@ + + + preferencesDialog + + + + 0 + 0 + 892 + 792 + + + + + 0 + 0 + + + + Preferences + + + + :/images/RTAB-Map.ico:/images/RTAB-Map.ico + + + + 0 + + + 0 + + + + + + + + 180 + 0 + + + + QAbstractItemView::NoEditTriggers + + + false + + + + + + + 6 + + + + + + 600 + 16777215 + + + + QFrame::Raised + + + 0 + + + + + + + 0General settings (GUI) + + + + + + + + + true + + + + + + + Keep images received. Used to show the loop closure image. (The cache can be cleared with the related action in the Edit menu) + + + true + + + + + + + 1 + + + QComboBox::AdjustToContents + + + + DEBUG + + + + + INFO + + + + + WARNING + + + + + ERROR + + + + + + + + Logger level + + + + + + + + + + true + + + + + + + Logger print time + + + + + + + 2 + + + QComboBox::AdjustToContents + + + + No log + + + + + Console + + + + + File + + + + + + + + Logger type: +when using the file type, logs are saved in Logrtabmap.txt (located in the working directory) + + + true + + + + + + + + + + + + + + Vertical layout used for the main window + + + false + + + + + + + + + + true + + + + + + + Show image when an hypothesis is rejected + + + true + + + + + + + Flip image (as like a mirror) + + + false + + + + + + + + + + + + + + Logger event sent level (fatal messages are always sent) + + + true + + + + + + + 1 + + + QComboBox::AdjustToContents + + + + DEBUG + + + + + INFO + + + + + WARNING + + + + + ERROR + + + + + + + + Logger level on which the process will automatically pause, showing the log console. A level set under the logger event level will do nothing. + + + true + + + + + + + 4 + + + QComboBox::AdjustToContents + + + + DEBUG + + + + + INFO + + + + + WARNING + + + + + ERROR + + + + + FATAL + + + + + + + + Beep! on special events (finished processing the data set, an error has occured, ...) + + + true + + + + + + + + + + + + + + 0 + + + 100 + + + 16 + + + Qt::Horizontal + + + + + + + Keypoints opacity + + + true + + + + + + + + + + Save/load configurations + + + + + + Save all configurations to ... + + + + + + + Load all configurations from ... + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 0Source + + + + + + + + 0 + + + + Usb device (Webcam) + + + + + Images (ex. 1.jpg, 2.jpg...) + + + + + Video file (AVI) + + + + + Database + + + + + + + + Source type + + + + + + + 1 + + + 100.000000000000000 + + + 0.100000000000000 + + + 1.000000000000000 + + + + + + + Image rate (img/s) + + + + + + + + + + + + + + Auto restart + + + + + + + Image width (set to 0 to use the default size of the source) + + + true + + + + + + + Image height (set to 0 to use the default size of the source) + + + true + + + + + + + 0 + + + 999999999 + + + 160 + + + 0 + + + + + + + 0 + + + 999999999 + + + 120 + + + 0 + + + + + + + + + 1 + + + + + + + Usb device + + + + + + -1 + + + 999999999 + + + 0 + + + + + + + Usb device + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Images dataset + + + + + + ... + + + + + + + false + + + + + + + 0 + + + 999999999 + + + + + + + Start position (default 1, 0=start from the last) + + + + + + + + + + + + + + Refresh the directory files list after each image loaded + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Video (AVI) + + + + + + ... + + + + + + + false + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Database (Sqlite3) + + + + + + ... + + + + + + + false + + + + + + + + + + + + + + Ignore children signatures + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + 0RTAB-Map settings + + + + + + + + + true + + + + + + + Publish statistics + + + + + + + 10.000000000000000 + + + 1.000000000000000 + + + + + + + Max time allowed (sec) + + + + + + + 999 + + + 1 + + + + + + + Images buffer size (0 means inf) + + + true + + + + + + + 999 + + + 1 + + + 30 + + + + + + + Minimum size of the working memory to create loop closure hypotheses + + + true + + + + + + + 1.000000000000000 + + + 0.010000000000000 + + + 0.950000000000000 + + + + + + + Loop closure threshold + + + true + + + + + + + The loop closure hypothesis must be over LoopRatio x lastHypothesisValue + + + true + + + + + + + 1.000000000000000 + + + 0.010000000000000 + + + 0.700000000000000 + + + + + + + true + + + + + + + Working directory (where the database is saved/loaded) + + + + + + + ... + + + + + + + 999 + + + 1 + + + 2 + + + + + + + Maximum locations retrieved at the same time from LTM to WM. + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 1Bayes filter + + + + + + + 0 + 100 + + + + Prediction probabilities for each loop closure event: +We can edit how will like the prediction used in the Bayes filter when there is a loop closure hypothesis. The values of the sliders are in %. LC is the loop closure event. +- values are the probabilities to be at one of the neighbours of the LC after a loop closure on LC. VP (virtual place) is the probability to be in a new place since a loop closure was found on the last iteration. + + + true + + + + + + + + + + + VP + + + Qt::AlignCenter + + + + + + + + + Qt::Vertical + + + + + + + + + 0 + + + Qt::AlignCenter + + + + + + + + + + 0 + 50 + + + + true + + + + + 0 + 0 + 518 + 147 + + + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 141 + 20 + + + + + + + + + + + + + LC + + + Qt::AlignCenter + + + + + + + + + Qt::Vertical + + + + + + + + + 0 + + + Qt::AlignCenter + + + + + + + + + + + + Qt::Horizontal + + + + 141 + 20 + + + + + + + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Neighbors + + + + + + + 9 + + + 2 + + + + + + + Prediction + + + + + + + true + + + + + + + Sum + + + + + + + + + + + + + + Preview + + + + + + + + 100 + 100 + + + + + + + + + + Prediction probability for the no loop closure event: +The VP here is the probability to be in a new place since no loop closure was found on the last iteration. + + + true + + + + + + + + + Virtual place prior + + + true + + + + + + + 1.000000000000000 + + + 0.010000000000000 + + + 0.800000000000000 + + + + + + + + + (NOTE) If you want to use the virtual place (VP) probability, the option "Common signature used" in the Memory panel must be set to true (this will update the common signature representing the virtual place) + + + true + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + + + + + + 1Memory strategy + + + + + + + 50 + 0 + + + + 1.000000000000000 + + + 0.010000000000000 + + + 0.150000000000000 + + + + + + + Retrieval threshold + + + false + + + + + + + + + + + + + + Disable memory retrieval + + + false + + + + + + + + + + + + + + Retrieved words are compared to the last words added in the dictionary (which are not yet indexed, may be add a time overhead for a minor likelihood improvement) + + + true + + + + + + + 1 + + + 9999 + + + 15 + + + + + + + Short-time memory size + + + false + + + + + + + + + + true + + + + + + + Incremental Memory + + + false + + + + + + + + + + false + + + + + + + Common signature used (virtual place) + + + false + + + + + + + (iterations) Delay required to transfer a signature to the database + + + false + + + + + + + + 50 + 0 + + + + + + + + Clean the neighborhood of the retrieved id + + + false + + + + + + + + + + false + + + + + + + + 50 + 0 + + + + 1.000000000000000 + + + 0.010000000000000 + + + 0.200000000000000 + + + + + + + Ratio of locations after the last loop closure in WM that cannot be transferred + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 2Database + + + + + + + + + true + + + + + + + Keep raw data (images) + + + true + + + + + + + + + + true + + + + + + + 999 + + + 20 + + + + + + + 9999 + + + 10 + + + 200 + + + + + + + + + + + + + + 10 + + + 999999 + + + 100 + + + 2000 + + + + + + + Sqlite3 cache size, +see Sqlite3 doc 'PRAGMA cache_size' + + + true + + + + + + + + DELETE + + + + + TRUNCATE + + + + + PERSIST + + + + + MEMORY + + + + + OFF + + + + + + + + Sqlite3 journal mode, +see Sqlite3 doc 'PRAGMA journal_mode' + + + true + + + + + + + Using database in the memory instead of a file on the hard disk (this greatly improve database access performance but it requires more RAM memory) + + + true + + + + + + + (KeypointMemory) Minimum visual words needed in the trash to save them. The trash thread will save in a chunk instead of small database queries. + + + true + + + + + + + Minimum signatures needed in the trash to save them. The trash thread will save in a chunk instead of small database queries. + + + true + + + + + + + Database auto cleaning, +delete old signatures in the database after run (the ones which will never be reactivated) + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 2Rehearsal + + + + + + 1.000000000000000 + + + 0.010000000000000 + + + 0.500000000000000 + + + + + + + Similarity threshold (set to 1 to disable rehearsal) + + + + + + + Only compare to the last signature in STM, otherwise all signatures in STM are compared. + + + true + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 1Signature + + + + + + 0 + + + 2000 + + + 150 + + + + + + + 0 means that the response (hessian) threshold +used for the detector will not be adapted. +Otherwise, the threshold is modified to +generate the number of words requested. + + + Words per image target + + + true + + + + + + + 2 + + + 0.000000000000000 + + + 1.000000000000000 + + + 0.050000000000000 + + + 0.250000000000000 + + + + + + + 0 means that the response (hessian) threshold +used for the detector will not be adapted. +Otherwise, the threshold is modified to +generate the number of words requested. + + + Bad signature ratio (less than Ratio x AverageWordsPerImage = bad) + + + true + + + + + + + + + + true + + + + + + + Use adaptive response threshold + + + true + + + + + + + + SURF + + + + + Star + + + + + SIFT + + + + + + + + Keypoint detector + + + true + + + + + + + + SURF + + + + + Color+SURF + + + + + Laplacian+SURF + + + + + SIFT + + + + + Hue+SURF + + + + + + + + Keypoint descriptor + + + true + + + + + + + + + + true + + + + + + + Use the tf-idf method to compute the likelihood + + + true + + + + + + + + + + true + + + + + + + If the dictionary update and signature creation were parallelized + + + true + + + + + + + % + + + 0 + + + + + + + Left ROI ratio (0 = no change) + + + true + + + + + + + % + + + 0 + + + + + + + % + + + 0 + + + + + + + % + + + 0 + + + + + + + Right ROI ratio (0 = no change) + + + true + + + + + + + Top ROI ratio (0 = no change) + + + true + + + + + + + Bottom ROI ratio (0 = no change) + + + true + + + + + + + true + + + + + + + ROI ratios [left, right, top, bottom] + + + true + + + + + + + If tf-idf weighting is normalized by the words count ratio between compared signatures + + + true + + + + + + + + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 2SURF detector + + + + + + 0 + + + 50000.000000000000000 + + + 500.000000000000000 + + + + + + + Hessian threshold + + + + + + + 1 + + + 100000 + + + 4 + + + + + + + Octaves + + + + + + + 1 + + + 100000 + + + 2 + + + + + + + Octave layers + + + + + + + true + + + + + + + + + + GPU version (as well for descriptor) - OpenCV must be compiled with CUDA from NVidia, otherwise, the application may crash if set. + + + true + + + + + + + true + + + + + + + + + + U-SURF used (only with GPU version) + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 2Star detector + + + + + + 1 + + + 128 + + + 45 + + + + + + + Maximum size + + + + + + + 1 + + + 100000 + + + 30 + + + + + + + Response threshold + + + + + + + 1 + + + 100000 + + + 10 + + + + + + + Line threshold projected + + + + + + + 1 + + + 100000 + + + 8 + + + + + + + Line threshold binarized + + + + + + + 1 + + + 100000 + + + 5 + + + + + + + Suppress Nonmax size + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 2SIFT detector + + + + + + 6 + + + 0.000000000000000 + + + 50000.000000000000000 + + + 0.001000000000000 + + + 0.006667000000000 + + + + + + + Threshold + + + + + + + Edge threshold + + + + + + + 0.100000000000000 + + + 10.000000000000000 + + + + + + + + + + Qt::Vertical + + + + 20 + 579 + + + + + + + + + + + + 2SURF descriptor + + + + + + + + + + + + + Descriptor extended (true=128, false=64) + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 2Dictionary + + + + + + QComboBox::AdjustToContents + + + + Naive + + + + + KdTree OpenCV + + + + + KdForest FLANN + + + + + + + + Nearest neighbor strategy + + + + + + + + + + true + + + + + + + Use an incremental dictionary + + + + + + + 999.990000000000009 + + + 0.010000000000000 + + + 0.160000000000000 + + + + + + + Minimum distance (for the nearest neighbor) +Lower the distance -> higher the precision + + + false + + + + + + + + + + true + + + + + + + 999.990000000000009 + + + 0.010000000000000 + + + 0.700000000000000 + + + + + + + NNDR ratio +(A matching pair is accepted, if its distance is closer than X times the distance of the second nearest neighbor) +Lower the ratio -> higher the precision + + + true + + + + + + + Use nearest neighbor distance ratio approach (NNDR) + + + true + + + + + + + Use a minimum distance for the nearest neighbor + + + true + + + + + + + + + + true + + + + + + + Maximum number of leafs checked (when using kd-trees) + + + true + + + + + + + 1024 + + + 64 + + + + + + + + + + ... + + + + + + + Path to a pre-computed dictionary (when "Use an incremental dictionary" is not set) + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + 0 + 0 + + + + 1Hypotheses verification strategies + + + + + + QComboBox::AdjustToContents + + + + No verification + + + + + Epipolar constraints + + + + + + + + Hypotheses verification + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 2Epipolar constraints + + + + + + 8 + + + 100000 + + + 11 + + + + + + + Minimum match count to accept a loop closure + + + true + + + + + + + 1 + + + 10.000000000000000 + + + 0.100000000000000 + + + 3.000000000000000 + + + + + + + Fundamental Matrix : Distance (pixels) for inliers + + + true + + + + + + + 2 + + + 0.990000000000000 + + + 0.010000000000000 + + + 0.990000000000000 + + + + + + + Fundamental Matrix : Ransac performance + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::RestoreDefaults + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + rtabmap::Plot + QWidget +
Plot.h
+ 1 +
+
+ + + + + + verticalSlider_prediction_lp + valueChanged(int) + label_prediction_lp + setNum(int) + + + 508 + 179 + + + 509 + 233 + + + + + verticalSlider_prediction_vp + valueChanged(int) + label_prediction_vp + setNum(int) + + + 314 + 185 + + + 315 + 241 + + + + +
diff --git a/lib/.empty b/lib/.empty new file mode 100644 index 0000000000..e69de29bb2