#include <visp3/visual_features/vpFeatureLuminanceMapping.h>
#include <visp3/core/vpSubMatrix.h>
#include <visp3/core/vpUniRand.h>
#include <visp3/core/vpIoTools.h>
#if defined(VISP_HAVE_CATCH2)
#define CATCH_CONFIG_RUNNER
#include <catch.hpp>
#ifdef ENABLE_VISP_NAMESPACE
#endif
vpMatrix orthogonalBasis(
unsigned n,
unsigned seed)
{
for (unsigned int row = 0; row < n; ++row) {
double norm = 0.0;
for (unsigned int col = 0; col < n; ++col) {
basis[row][col] = rand.
uniform(-1.0, 1.0);
norm += basis[row][col] * basis[row][col];
}
norm = 1.0 / sqrt(norm);
for (unsigned int col = 0; col < n; ++col) {
basis[row][col] *= norm;
}
}
for (unsigned i = 1; i < n; ++i) {
for (unsigned j = 0; j < i; ++j) {
for (unsigned k = 0; k < n; ++k) {
basis[i][k] -= res[k];
}
}
}
for (unsigned int row = 0; row < n; ++row) {
double norm = sqrt(norms[row]);
for (unsigned int col = 0; col < n; ++col) {
basis[row][col] /= norm;
}
}
return basis;
}
SCENARIO("Using PCA features", "[visual_features]")
{
GIVEN("A matrix containing simple data")
{
const unsigned h = 16, w = 16;
const unsigned numDataPoints = 4;
const unsigned int dataDim = h * w;
const unsigned int trueComponents = 3;
const vpMatrix orthoFull = (orthogonalBasis(dataDim, 42) +
vpMatrix(dataDim, dataDim, 1.0)) * 127.5;
const vpMatrix ortho(orthoFull, 0, 0, trueComponents, dataDim);
const vpMatrix coefficients(numDataPoints, trueComponents);
for (unsigned int i = 0; i < coefficients.getRows(); ++i) {
double sum = 0.0;
for (unsigned int j = 0; j < coefficients.getCols(); ++j) {
coefficients[i][j] = rand.uniform(0.0, 1.0);
sum += coefficients[i][j] * coefficients[i][j];
}
const double inv_norm = 1.0 / sqrt(sum);
for (unsigned int j = 0; j < coefficients.getCols(); ++j) {
coefficients[i][j] *= inv_norm;
}
}
WHEN("Learning PCA basis with too many components")
{
unsigned int k = data.
getCols() + 1;
THEN("An exception is thrown")
{
}
}
WHEN("Learning with more images than pixels")
{
THEN("An exception is thrown")
{
}
}
WHEN("Learning PCA basis")
{
for (unsigned int k = 2; k <= trueComponents; ++k) {
THEN("Basis has correct dimensions")
{
REQUIRE(basis.
getCols() == dataDim);
}
THEN("The basis is orthonormal")
{
bool matrixSame = true;
for (
unsigned int row = 0; row < I.
getRows(); ++row) {
for (
unsigned int col = 0; col < I.
getCols(); ++col) {
if (fabs(I[row][col] - Iapprox[row][col]) > 1e-6) {
matrixSame = false;
break;
}
}
}
REQUIRE(matrixSame);
}
THEN("Mean vector has correct dimensions")
{
REQUIRE(pca.
getMean()->getRows() == dataDim);
REQUIRE(pca.
getMean()->getCols() == 1);
}
THEN("Modifying the basis size (number of inputs) by hand and saving")
{
REQUIRE_THROWS(pca.
save(basisFile, meanFile, varFile));
}
THEN("Modifying the mean Columns by hand")
{
std::shared_ptr<vpColVector> mean = pca.
getMean();
mean->resize(mean->getRows() + 1, false);
REQUIRE_THROWS(pca.
save(basisFile, meanFile, varFile));
}
THEN("Saving and loading pca leads to same basis and mean")
{
pca.
save(basisFile, meanFile, varFile);
bool basisSame = true;
bool meanSame = true;
bool explainedVarSame = true;
for (
unsigned int i = 0; i < basisDiff.
getRows(); ++i) {
for (
unsigned int j = 0; j < basisDiff.
getCols(); ++j) {
if (fabs(basisDiff[i][j]) > 1e-10) {
basisSame = false;
break;
}
}
}
REQUIRE(basisSame);
for (
unsigned int i = 0; i < meanDiff.
getRows(); ++i) {
if (fabs(meanDiff[i]) > 1e-10) {
std::cout << meanDiff << std::endl;
meanSame = false;
break;
}
}
REQUIRE(meanSame);
for (
unsigned int i = 0; i < explainedVarDiff.
getRows(); ++i) {
if (fabs(explainedVarDiff[i]) > 1e-10) {
explainedVarSame = false;
break;
}
}
REQUIRE(explainedVarSame);
}
THEN("Explained variance is below 1 and sorted in descending order")
{
REQUIRE(var.
sum() < 1.0);
for (
int i = 1; i < (int)var.
getRows() - 1; ++i) {
REQUIRE(var[i] >= var[i + 1]);
}
}
if (k == trueComponents) {
WHEN("K is the true manifold dimensionality")
{
THEN("explained variance is close to 1")
{
}
THEN("Inverse mapping leads back to the same data")
{
for (unsigned int i = 0; i < numDataPoints; ++i) {
for (
unsigned int j = 0; j < data.
getCols(); ++j) {
I.
bitmap[j] =
static_cast<unsigned char>(data[i][j]);
}
for (
unsigned int j = 0; j < data.
getCols(); ++j) {
REQUIRE(abs(
static_cast<int>(I.
bitmap[j]) -
static_cast<int>(Irec.
bitmap[j])) < 2);
}
}
}
}
}
THEN("Projecting data is correct")
{
{
}
{
const unsigned border = 3;
}
}
}
}
}
WHEN("Saving unintialized PCA")
{
THEN("an exception is thrown")
{
REQUIRE_THROWS(pca.
save(basisFile, meanFile, varFile));
}
}
}
SCENARIO("Using DCT features", "[visual_features]")
{
GIVEN("A matrix")
{
std::vector<std::tuple<vpMatrix, vpColVector, vpMatrix>> data = {
{
{0.0, 1.0, 2.0},
{3.0, 4.0, 5.0},
{6.0, 7.0, 8.0}
}),
{ 0.0, 1.0, 3.0, 6.0, 4.0, 2.0, 5.0, 7.0, 8.0 }
),
{0.0, 1.0, 5.0},
{2.0, 4.0, 6.0},
{3.0, 7.0, 8.0}
})
}
};
for (unsigned int i = 0; i < data.size(); ++i) {
WHEN("Building the associated zigzag indexing matrix")
{
const vpMatrix mAfterWriterVec = std::get<2>(data[i]);
THEN("Calling getValues with wrong matrix rows throws")
{
REQUIRE_THROWS(zigzag.
getValues(wrongM, 0, 2, s));
}
THEN("Calling getValues with wrong matrix cols throws")
{
REQUIRE_THROWS(zigzag.
getValues(wrongM, 0, 2, s));
}
THEN("Calling getValues with wrong start and end arguments throws")
{
REQUIRE_THROWS(zigzag.
getValues(m, 2, 1, s));
}
THEN("Calling getValues and querying all values returns correct result")
{
REQUIRE(s == contentAsZigzag);
}
THEN("Calling getValues and querying a subset of the values is correct")
{
REQUIRE(s == contentAsZigzag.
extract(0, m.
size() / 2));
}
THEN("Calling setValues with wrong matrix rows throws")
{
REQUIRE_THROWS(zigzag.
setValues(contentAsZigzag, 0, wrongM));
}
THEN("Calling setValues with wrong matrix cols throws")
{
REQUIRE_THROWS(zigzag.
setValues(contentAsZigzag, 0, wrongM));
}
THEN("Calling setValues with wrong start and vector size arguments throws")
{
REQUIRE_THROWS(zigzag.
setValues(contentAsZigzag, m.
size() - contentAsZigzag.
size() + 1, m));
}
THEN("Calling setValues leads to expected result")
{
for (
unsigned i = 0; i < powered.
size(); ++i) {
powered[i] *= powered[i];
}
REQUIRE_NOTHROW(zigzag.
setValues(powered, 0, mWrite));
REQUIRE_NOTHROW(zigzag.
getValues(mWrite, 0, mWrite.size(), poweredRead));
REQUIRE(powered == poweredRead);
for (
unsigned i = 0; i < powered.
size(); ++i) {
indices[i] = static_cast<double>(i);
}
REQUIRE_NOTHROW(zigzag.
setValues(indices, 0, mWrite));
REQUIRE(mWrite == mAfterWriterVec);
REQUIRE(s2 == contentAsZigzag.
extract(0, 3));
}
}
}
GIVEN("A constant image")
{
WHEN("Computing DCT")
{
dct.setBorder(0);
dct.map(I, s);
THEN("resulting feature vector has correct size")
{
}
THEN("The only non zero component is the first")
{
REQUIRE(s.
sum() == Approx(s[0]).margin(1e-5));
}
dct.inverse(s, Ir);
for (
unsigned i = 0; i < I.
getRows(); ++i) {
for (
unsigned j = 0; j < I.
getCols(); ++j) {
const int diff = abs(static_cast<int>(I[i][j]) - static_cast<int>(Ir[i][j]));
REQUIRE(diff < 2);
INFO("i = " + std::to_string(i) + ", j = " + std::to_string(j));
}
}
}
}
}
}
int main(int argc, char *argv[])
{
Catch::Session session;
session.applyCommandLine(argc, argv);
int numFailed = session.run();
return numFailed;
}
#else
int main()
{
return EXIT_SUCCESS;
}
#endif
unsigned int getCols() const
unsigned int size() const
Return the number of elements of the 2D array.
unsigned int getRows() const
Implementation of column vector and the associated operations.
vpColVector extract(unsigned int r, unsigned int colsize) const
unsigned int getCols() const
Type * bitmap
points toward the bitmap
unsigned int getRows() const
Helper class to iterate and get/set the values from a matrix, following a zigzag pattern.
void init(unsigned rows, unsigned cols)
Initalize the ZigZag object. Computes and stores the zigzag indexing for a given matrix size.
void setValues(const vpColVector &s, unsigned int start, vpMatrix &m) const
set the values in the matrix, according to the values stored in the vector s and the zigzag indexing ...
void getValues(const vpMatrix &m, unsigned int start, unsigned int end, vpColVector &s) const
Fill the vector s with (end - start) values, according to the zigzag matrix indexing strategy.
unsigned int getProjectionSize() const
Returns the size of the space to which an image is mapped to.
unsigned int getBorder() const
Returns the number of pixels that are removed by the photometric VS computation.
void setBorder(unsigned border)
Set the number of pixels that are removed by the photometric VS computation This function should be c...
static vpLuminancePCA learn(const std::vector< std::string > &imageFiles, const unsigned int projectionSize, const unsigned int imageBorder=0)
Compute a new Principal Component Analysis on set of images, stored on disk.
void inverse(const vpColVector &s, vpImage< unsigned char > &I) VP_OVERRIDE
Reconstruct I from a representation s.
void save(const std::string &basisFilename, const std::string &meanFileName, const std::string &explainedVarianceFile) const
Save the PCA basis to multiple text files, for later use via the load function.
std::shared_ptr< vpColVector > getMean() const
Get , the mean image computed from the dataset.
void map(const vpImage< unsigned char > &I, vpColVector &s) VP_OVERRIDE
Map an image I to a representation s. This representation s has getProjectionSize() rows.
std::shared_ptr< vpMatrix > getBasis() const
Get , the subspace projection matrix ( )
static vpLuminancePCA load(const std::string &basisFilename, const std::string &meanFileName, const std::string &explainedVarianceFile)
Save the PCA basis to multiple text files, for later use via the load function.
vpColVector getExplainedVariance() const
Get the values of explained variance by each of the eigen vectors.
Implementation of a matrix and operations on matrices.
vpRowVector getRow(unsigned int i) const
vpMatrix transpose() const
Implementation of row vector and the associated operations.
Class for generating random numbers with uniform probability density.
int uniform(int a, int b)