/**
 * @file scene.cc
 *
 * @auhtor Remus Claudiu Dumitru <r.dumitru@jacobs-university.de>
 * @date 13 Feb 2012
 *
 */

//==============================================================================
//  Includes
//==============================================================================
#include "model/scene.h"

#include "model/graphicsAlg.h"
#include "model/util.h"

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>

#include <boost/algorithm/string.hpp>

#include <sys/stat.h>
#include <errno.h>

#include <limits>
#include <iomanip>
#include <fstream>
#include <sstream>
using namespace std;

//==============================================================================
//  Static fields initialization
//==============================================================================
const double model::Scene::PRECISION    = 0.0;
const double model::Scene::RAY_DIST     = 2.5;
const double model::Scene::PATCH_DIST   = 1.0;
const double model::Scene::WALL_DIST    = 10.0; // must be bigger than RAY_DIST
const double model::Scene::MIN_EDGE_COV = 0.60;

//==============================================================================
//  Implementation
//==============================================================================
model::Scene::Scene(const IOType& type,
				const int& start, const int& end,
				std::string dir, const bool& scanserver,
				const int& maxDist,  const int& minDist,
				const PlaneAlgorithm& alg, const int& octree, const double& red,
				const vector<Pose6d>& poses)
{
    if (!quiet) cout << endl << "== Creating scene..." << endl;
    if (!quiet) cout << endl << "== Reading scans..." << endl;

    // create an output directory for planes and for images
    if (makeDir(dir + "img/")== false) {
    	throw runtime_error("failed to create directory " + dir + "img/");
    }

    // copy class fields here
    this->poses = poses;

    // identity matrix
    double id[16];
    M4identity(id);

    // begin the plane detection
    //    Scan::dir = dir;
    //    Scan::readScans(type, start, end, dir, maxDist, minDist, false);
    Scan::openDirectory(scanserver, dir, type, start, end);

    if(Scan::allScans.size() == 0) {
	 cerr << "No scans found. Did you use the correct format?" << endl;
	 exit(-1);
    }
    
    int nrPlanes = 0 , currScan = start;
    //    for(vector <Scan*>::iterator scan = Scan::allScans.begin(); scan != Scan::allScans.end(); ++scan) {
    for(ScanVector::iterator scan = Scan::allScans.begin(); scan != Scan::allScans.end(); ++scan) {
     
        // prepare for plane detection
	   (*scan)->setRangeFilter(maxDist, minDist);
	   (*scan)->setReductionParameter(red, octree);
	   // scan->setSearchTreeParameter(nns_method, cuda_enabled);

        (*scan)->toGlobal();
        (*scan)->transform(id, Scan::ICP, 0);

        // read the pose of the current scan
        pair<Vector3d, Rotation3d> pose;
        const double* pt  = (*scan)->get_rPos();
        const double* rot = (*scan)->get_rPosTheta();

        pose.first.x = pt[0];
        pose.first.y = pt[1];
        pose.first.z = pt[2];

        pose.second.x = rot[0];
        pose.second.y = rot[1];
        pose.second.z = rot[2];

        if (!quiet) cout << "\n== Starting plane detection for scan " << currScan << " @ pose ("
                << pose.first.x << " " << pose.first.y << " " << pose.first.z << " "
                << pose.second.x << " " << pose.second.y << " " << pose.second.z << ")..." << endl;

        // apply hough transform
        Hough hough((*scan), true);
        nrPlanes += hough.RHT();    // TODO call proper algorithm

        if (!quiet) cout << "** Detected " << nrPlanes << " planes so far" << endl;

        // loop over all planes and fill in the data
        for (vector<ConvexPlane*>::iterator convexPlane = hough.planes.begin();
                convexPlane != hough.planes.end(); convexPlane++)
        {
            // get the nomrla and the point of application of the normal
            double tempNormal[3], tempOrigin[3];
            (*convexPlane)->getNormal(tempNormal, tempOrigin);

            // the normal and application points using our datastructures
            Vector3d normal(tempNormal[0], tempNormal[1], tempNormal[2]);
            Point3d origin(tempOrigin[0], tempOrigin[1], tempOrigin[2]);

            // fill in the convex hulls
            vector<double> doubleHull = (*convexPlane)->getConvexHull();
            vector<Point3d> pointHull;

            for (vector<double>::iterator point = doubleHull.begin(); point != doubleHull.end(); point += 3) {
                pointHull.push_back(Point3d(*(point+0), *(point+1), *(point+2)));
            }

            Plane3d planeToAdd(origin, normal, pointHull);

            // add the planes to the vector
            if (planeToAdd.isHorizontal() || planeToAdd.isVertical()) {
                this->planes.push_back(planeToAdd);
            }
            else if (!quiet) {
                cout << "** Discarding oblique plane centered at "
                        << planeToAdd.pt.x << " " << planeToAdd.pt.y << " " << planeToAdd.pt.z
                        << endl;
            }
        }

        // keep the pointer to the points
	   DataXYZ points = (*scan)->get("xyz reduced");
	   unsigned int points_size = points.size();
	   //        const vector<Point>* points = (*scan)->get_points();

        for (unsigned int i = 0; i < points_size; ++i) {
            // XXX get_points returns a pointer
            Point3d toPush(points[i][0], points[i][1], points[i][2]);
            toPush.translate(pose.first);
            toPush.rotate(Point3d(0.0, 0.0, 0.0), pose.second);
            this->points.push_back(toPush);
        }

        // contains the current scan number
        currScan++;
    }

    // to be used when constructing the octree
    this->octTreePoints = new double*[this->points.size()];

    // go over all points from all the scans
    for (unsigned int i = 0; i < this->points.size(); ++i) {
        double x = this->points[i].x;
        double y = this->points[i].y;
        double z = this->points[i].z;

        // add the points to the array to be added to the octree
        this->octTreePoints[i] = new double[3];
        this->octTreePoints[i][0] = x;
        this->octTreePoints[i][1] = y;
        this->octTreePoints[i][2] = z;
    }

    // Create the octree and fill it in.
    if (!quiet) cout << endl << "== Building OctTree with " << this->points.size() << " points ..." << endl;
    this->octTree = new BOctTree<double>(this->octTreePoints, this->points.size(), octree);
    //this->_octTree->init();
    this->nrPoints = this->points.size();

    Scan::allScans.clear();
}

model::Scene::Scene(const Scene& other) {
    this->points  = other.points;
    this->planes  = other.planes;
    this->walls   = other.walls;
    this->ceiling = other.ceiling;
    this->floor   = other.floor;
}

model::Scene::~Scene() {
    // delete the octree
    if (this->octTree != NULL) {
        delete this->octTree;
    }

    if (this->octTreePoints != NULL) {
        for (unsigned int i = 0; i < this->nrPoints; ++i) {
            if (this->octTreePoints[i] != NULL) {
                delete[] this->octTreePoints[i];
            }
        }
        delete[] this->octTreePoints;
    }
}

vector<model::Plane3d> model::Scene::getConvexHull(vector<Plane3d> planes) {
    if (planes.size() <= 3) {
        return planes;
    }

    vector<Plane3d> hull;
    vector<Plane3d>::iterator leftmostIt = planes.begin();

    // compute the lefmost point
    for (vector<Plane3d>::iterator it = planes.begin() + 1;
            it != planes.end(); ++it)
    {
        // add only vertical planes
        if (it->isVertical() && it->pt.x < leftmostIt->pt.x) {
            leftmostIt = it;
        }
    }

    Plane3d planeOnHull = *leftmostIt;
    Plane3d endPlane;

    do {
        // add the candidate plane to the hull
        hull.push_back(planeOnHull);
        endPlane = planes[0];

        for (unsigned int j = 1; j < planes.size(); ++j) {
            // consider only vertical planes
            if (planes[j].isVertical() == false) {
                continue;
            }

            // define dummy planes in the XOZ plane
            Point3d fakeCurPt(planes[j].pt.x, 0.0, planes[j].pt.z);
            Point3d fakePointOnHull(planeOnHull.pt.x, 0.0, planeOnHull.pt.z);
            Point3d fakeEndPoint(endPlane.pt.x, 0.0, endPlane.pt.z);

            // define a point to the left of the line
            Point3d farLeft = fakeEndPoint;
            farLeft.rotate(planeOnHull.pt, Rotation3d(0.0, -M_PI/6, 0.0));

            // if ... or planes[j] on the left side of line (planeOnHull, endPlane)
            if (planeOnHull.pt != planes[j].pt &&
                    (endPlane.pt == planeOnHull.pt || sameSide(fakeCurPt, farLeft, fakePointOnHull, fakeEndPoint)))
            {
                endPlane = planes[j];
            }
        }

        planeOnHull = endPlane;

    } while (endPlane.pt != hull[0].pt);

    return hull;
}

vector<model::Plane3d> model::Scene::getSignificantPlanes(vector<Plane3d> planes) {
    vector<model::Plane3d> result;

    while (!planes.empty()) {
        // add the first plane as viable
        result.push_back(planes.front());
        planes.erase(planes.begin());;

        for (vector<Plane3d>::iterator it = planes.begin(); it < planes.end(); ++it) {
            if (result.back().isSamePlane(*it)) {
                result.back().normal += it->normal;
                planes.erase(it);
            }
        }

        // normalize this normal after we have normalized
        result.back().normal.normalize();
    }

    return result;
}

bool model::Scene::getCeiling(vector<Plane3d> planes, Plane3d& result) {
    vector<Plane3d> horizontalPlanes;

    for (vector<Plane3d>::iterator it = planes.begin(); it != planes.end(); ++it) {
        if (it->isHorizontal()) {
            horizontalPlanes.push_back(*it);
        }
    }

    if (horizontalPlanes.size() <= 0) {
        return false;
    }

    // start with the first horizontal plane
    result = planes.front();

    for (vector<Plane3d>::iterator it = planes.begin()+1;
            it != planes.end(); ++it)
    {
        if (it->pt.y > result.pt.y) {
            result = *it;
        }
    }

    return true;
}

bool model::Scene::getFloor(vector<Plane3d> planes, Plane3d& result) {
    vector<Plane3d> horizontalPlanes;

    for (vector<Plane3d>::iterator it = planes.begin();
            it != planes.end(); ++it)
    {
        if (it->isHorizontal()) {
            horizontalPlanes.push_back(*it);
        }
    }

    if (horizontalPlanes.size() <= 0) {
        return false;
    }

    // start with the first horizontal plane
    result = planes.front();

    for (vector<Plane3d>::iterator it = planes.begin()+1;
            it != planes.end(); ++it)
    {
        if (it->pt.y < result.pt.y) {
            result = *it;
        }
    }

    return true;
}

void model::Scene::detectWalls() {
    if (!quiet) cout << endl << "== Building model..." << endl;

    // temporary wall storage
    vector<Plane3d> tempWalls = this->planes;

    // extract the convex hull of the room
    tempWalls = GraphicsAlg::getConcaveHull(tempWalls);

    // throw away redundant tempWalls
    tempWalls = this->getSignificantPlanes(tempWalls);

    // get other bounds, ceiling and floor
    Plane3d tempCeiling, tempFloor;

    if (!this->getCeiling(this->planes, tempCeiling) || !this->getFloor(this->planes, tempFloor)) {
        throw runtime_error("error while determining ceiling or floor");
    }

    if (tempWalls.size() < 4) {
        throw runtime_error("room has less than 4 walls");
    }

    if (!quiet) cout << "** " << tempWalls.size() << " walls have been detected" << endl;

    // XXX planes in the _walls vector should be in order
    vector< pair<Point3d, Point3d> > corners;
    Point3d upperCorner, lowerCorner;

    if(!quiet) cout << endl << "== Computing corners..." << endl;
    for (vector<Plane3d>::iterator it = tempWalls.begin(); it != tempWalls.end(); ++it) {
        // be careful at the last element
        if (it < tempWalls.end()-1) {
            upperCorner = it->intersect(*(it+1), tempCeiling);
            lowerCorner = it->intersect(*(it+1), tempFloor);
        }
        else if (it == tempWalls.end()-1) {
            upperCorner = it->intersect(tempWalls.front(), tempCeiling);
            lowerCorner = it->intersect(tempWalls.front(), tempFloor);
        }

        if (!quiet) cout << showpos << "** Computed upper corner " << upperCorner << endl;
        if (!quiet) cout << showpos << "** Computed lower corner " << lowerCorner << endl;

        corners.push_back(make_pair(upperCorner, lowerCorner));
    }

    if (!quiet) cout << endl << "== Building walls, ceiling and floor..." << endl;

    // used to create the walls
    Point3d upperLeft, upperRight;
    Point3d lowerLeft, lowerRight;

    // used to create the floor and the ceiling
    vector<Point3d> ceilingHull;
    vector<Point3d> floorHull;

    Point3d ceilingCenter(0.0, 0.0, 0.0);
    Point3d floorCenter(0.0, 0.0, 0.0);

    Vector3d ceilingNormal;
    Vector3d floorNormal;

    for (vector< pair<Point3d, Point3d> >::iterator it = corners.begin();
            it != corners.end(); ++it)
    {
        upperLeft = it->first;
        lowerLeft = it->second;

        if (it < corners.end()-1) {
            upperRight = (it+1)->first;
            lowerRight = (it+1)->second;
        }
        else if (it == corners.end()-1) {
            upperRight = corners.front().first;
            lowerRight = corners.front().second;
        }

        Point3d center((upperLeft.x + lowerLeft.x + upperRight.x + lowerRight.x) / 4.0,
                (upperLeft.y + lowerLeft.y + upperRight.y + lowerRight.y) / 4.0,
                (upperLeft.z + lowerLeft.z + upperRight.z + lowerRight.z) / 4.0);

        vector<Point3d> hull;
        hull.push_back(upperLeft);
        hull.push_back(lowerLeft);
        hull.push_back(lowerRight);
        hull.push_back(upperRight);

        if (!quiet) cout << "** Adding wall centered at " << center << endl;
        this->walls.push_back(LabeledPlane3d(center, Vector3d(), hull));
        this->walls.back().normal = this->walls.back().computeAverageNormal();

        // compute the floor and the ceiling
        ceilingHull.push_back(it->first);
        floorHull.push_back(it->second);

        ceilingCenter.x += it->first.x;
        ceilingCenter.y += it->first.y;
        ceilingCenter.z += it->first.z;

        floorCenter.x += it->second.x;
        floorCenter.y += it->second.y;
        floorCenter.z += it->second.z;
    }
    ceilingCenter /= ceilingHull.size();
    floorCenter   /= floorHull.size();

    // dummy normal
    Vector3d dummy;

    if (!quiet) cout << "** Adding ceiling centered at " << ceilingCenter << endl;
    this->ceiling = LabeledPlane3d(ceilingCenter, dummy, ceilingHull);
    this->ceiling.normal = this->ceiling.computeAverageNormal();

    if (!quiet) cout << "** Adding floor centered at " << floorCenter << endl;
    this->floor = LabeledPlane3d(floorCenter, dummy, floorHull);
    this->floor.normal = this->floor.computeAverageNormal();
}

bool model::Scene::castRay(const Point3d& src, const Point3d& dest, const double& extraDist,
        Point3d& ptHit)
{
    // put the Bresenham line in here, we need some extra distance in case the points are close
    // but after the detected wall, therefore we need to go a bit further than the wall to check
    vector<Point3d> line;
    GraphicsAlg::getDiscreteLine(src, dest, PRECISION, extraDist, line);

    // loop through all the points in the Bresenham line and decide if it hits something
    for (vector<Point3d>::iterator it = line.begin(); it != line.end(); ++it) {
        if (this->isOccupied(*it, RAY_DIST)) {
            ptHit = *it;
            return true;
        }
    }

    // no occlusion took place
    return false;
}

void model::Scene::applyLabels(LabeledPlane3d& surf) {
    if (this->poses.empty()) {
        throw runtime_error("no poses have been provided to the scene");
    }

    // the maximum distance from which we subtract to create a depth image
    double maxDist = 2.0 * WALL_DIST + RAY_DIST;

    // determine the points on the plane for which to apply labels
    vector<vector<Point3d> > discretePoints = surf.getDiscretePoints(PATCH_DIST);

    surf.patches.resize(discretePoints.size());
    surf.depthMap.resize(discretePoints.size());

    // we need these values for later
    surf.depthMapDistances.first = WALL_DIST;
    surf.depthMapDistances.second = maxDist;

    for (unsigned int i = 0; i < discretePoints.size(); ++i) {
        for (unsigned int j = 0; j < discretePoints.front().size(); ++j) {
            // consider everything is occluded initially
            surf.patches[i].push_back(make_pair(discretePoints[i][j], OCCLUDED));
            surf.depthMap[i].push_back(WALL_DIST);
        }
    }

    if (!quiet) cout << endl << "== Performing ray casting for surface centered at " << surf.pt << endl;

    for (vector<Pose6d>::iterator srcPose = this->poses.begin(); srcPose != this->poses.end(); ++srcPose) {
        for (unsigned int i = 0; i < surf.patches.size(); ++i) {
            for (unsigned int j = 0; j <  surf.patches[i].size(); ++j) {
                // prepare two points for ray casting through wall
                Point3d ptOnWall  = surf.patches[i][j].first;
                Point3d src(surf.normal.x + ptOnWall.x,
                        surf.normal.y + ptOnWall.y,
                        surf.normal.z + ptOnWall.z);

                double len = ptOnWall.distance(src);
                double temp = (len + WALL_DIST) / len;
                src.x = ptOnWall.x + (src.x - ptOnWall.x) * temp;
                src.y = ptOnWall.y + (src.y - ptOnWall.y) * temp;
                src.z = ptOnWall.z + (src.z - ptOnWall.z) * temp;

                // remember the point we hit when ray casting
                Point3d ptHit;

                if (insideHull(surf.patches[i][j].first, surf.hull) && castRay(src, ptOnWall, WALL_DIST, ptHit)) {
                    surf.patches[i][j].second = OCCUPIED;
                    surf.depthMap[i][j] = maxDist - src.distance(ptHit);
                } else if (!castRay(srcPose->first, surf.patches[i][j].first, 0, ptHit)) {
                    surf.patches[i][j].second = EMPTY;
                    surf.depthMap[i][j] = 0.0;
                }
            }
        }
    }

    // create the OpenCV depth image
    int imgHeight = static_cast<int>(surf.depthMap.size());
    int imgWidth  = static_cast<int>(surf.depthMap.front().size());

    // crate an OpenCV image and allocate memory for it
    cv::Mat img(imgHeight, imgWidth, CV_8UC1);

    // compute the maximum from the image
    double max = numeric_limits<double>::min();
    for (int i = 0; i < imgHeight; ++i) {
        for (int j = 0; j < imgWidth; ++j) {
            if (max < surf.depthMap[i][j]) {
                max = surf.depthMap[i][j];
            }
        }
    }

    // write the current image to a file
    cv::Mat labels(imgHeight, imgWidth, CV_8UC3);

    // adjust the image
    for (int i = 0; i < imgHeight; ++i) {
        for (int j = 0; j < imgWidth; ++j) {
            img.data[(i * img.cols) + j] = static_cast<char>(MAX_IMG_VAL * surf.depthMap[i][j] / max);

            // put the color into the image according to the label
            int curr = (i * 3 * img.cols) + 3 * j;
            switch (surf.patches[i][j].second) {
            case EMPTY:
            	labels.data[curr + 0] = 0;
            	labels.data[curr + 1] = 200;
            	labels.data[curr + 2] = 0;
            	break;
            case OCCLUDED:
            	labels.data[curr + 0] = 200;
            	labels.data[curr + 1] = 0;
            	labels.data[curr + 2] = 0;
            	break;
            case OCCUPIED:
            	labels.data[curr + 0] = 0;
            	labels.data[curr + 1] = 0;
            	labels.data[curr + 2] = 200;
            	break;
            default:
					throw runtime_error("invalid default branch taken");
					break;
			}
        }
    }
    cv::imwrite("./img/labels.png", labels);

    // copy the image
    surf.depthImg = img.clone();
}

void model::Scene::applyAllLabels() {
    if (!quiet) cout << endl << "== Performing ray casting for all surfaces..." << endl;

    applyLabels(this->ceiling);
    applyLabels(this->floor);

    for (vector<LabeledPlane3d>::iterator it = this->walls.begin(); it != this->walls.end(); ++it) {
        applyLabels(*it);
    }
}

void model::Scene::detectPotentialOpenings(const LabeledPlane3d& surf,
        std::vector<CandidateOpening>& openings)
{
    if (!quiet) cout << endl << "== Determining openings for surface centered at " << surf.pt << endl;

    // get the candidate openings for current plane
    vector<CandidateOpening> candidates;
    surf.computeOpeningCandidates(candidates);

    // TODO train the SVM using some outside variables, not hard coded in here, play around a lot with these numbers
    // prepare to train the SVM

    const unsigned int nrSamples    = 68;
    const unsigned int nrMainClass  = 14;
    const unsigned int nrFeatures   = model::CandidateOpening::NR_FEATURES;

    // make a set of excluded features
    int excludedFeaturesInit[] = {6, 7, 8, 12, 13};
    set<int> excludedFeatures(excludedFeaturesInit, excludedFeaturesInit + 5);

    // set up the training data using some real life examples, needs to be flaot
    float trainingData[nrSamples][14] = {
            // TODO fix the RMS fit residual
            // 1 area | 2 w/h | 3 w/W | 4 h/H | 5-8 dist to edges | 9 RMS | 10-12 E Occup Occl | 13 int rect | 14 int U-shapes
            { 3120, 0.42, 0.05, 0.36,  23, 138, 550, 126, 0, 0.93, 0.01, 0.06, 15, 0},
            { 3090, 0.42, 0.05, 0.36,  23, 138, 126, 550, 0, 0.93, 0.01, 0.06, 15, 0},
            { 6810, 0.94, 0.11, 0.34,  23, 140, 453, 176, 0, 0.91, 0.02, 0.07, 15, 0},
            { 6815, 0.94, 0.11, 0.34,  23, 140, 176, 453, 0, 0.91, 0.02, 0.07, 15, 0},
            {11780, 1.52, 0.19, 0.35,  23, 139, 450, 128, 0, 0.89, 0.01, 0.10, 15, 0},
            {11190, 1.52, 0.18, 0.35,  23, 139, 128, 450, 0, 0.88, 0.02, 0.10, 15, 0},
            {10374, 1.70, 0.18, 0.31,  31, 141, 453, 124, 0, 0.87, 0.02, 0.11, 15, 0},
            {10374, 1.70, 0.18, 0.31,  31, 141, 124, 453, 0, 0.87, 0.02, 0.11, 15, 0},
            {27600, 0.82, 0.22, 0.62,  18,  97, 403, 148, 0, 0.95, 0.03, 0.02, 12, 0},
            {27600, 0.82, 0.22, 0.62,  18,  97, 148, 403, 0, 0.94, 0.03, 0.03, 12, 0},
            {25516, 0.87, 0.22, 0.59,  28, 100,  49, 396, 0, 0.84, 0.11, 0.05, 20, 0},
            {26316, 0.87, 0.22, 0.59,  28, 100, 396,  49, 0, 0.84, 0.13, 0.03, 22, 0},
            {25516, 0.87, 0.22, 0.59,  28, 100,  70, 396, 0, 0.84, 0.11, 0.05, 20, 0},
            {26316, 0.87, 0.22, 0.59,  28, 100, 396,  70, 0, 0.84, 0.13, 0.03, 22, 0},
            //========================================================================
            {+143289, +2.10345, +0.786533, +0.887755, +31, +1, +1, +147, +0, +0.371222, +0.524681, +0.104097, +34300, +0},
            {+180612, +2.65134, +0.991404, +0.887755, +31, +1, +1, +4, +0, +0.294571, +0.620845, +0.0845846, +44100, +0},
            {+180873, +2.65517, +0.992837, +0.887755, +31, +1, +1, +3, +0, +0.294146, +0.621392, +0.0844626, +55125, +0},
            {+181395, +2.66284, +0.995702, +0.887755, +31, +1, +1, +1, +0, +0.293299, +0.622481, +0.0842195, +67375, +0},
            {+181656, +2.66667, +0.997135, +0.887755, +31, +1, +1, +0, +0, +0.292878, +0.621846, +0.0852766, +80850, +0},
            {+181828, +2.64885, +0.994269, +0.891156, +31, +0, +0, +3, +0, +0.292634, +0.621114, +0.0862518, +70125, +2750},
            {+143838, +2.09542, +0.786533, +0.891156, +31, +0, +1, +147, +0, +0.369805, +0.525466, +0.104729, +35700, +1400},
            {+181304, +2.64122, +0.991404, +0.891156, +31, +0, +1, +4, +0, +0.293446, +0.621465, +0.0850891, +45900, +1800},
            {+181566, +2.64504, +0.992837, +0.891156, +31, +0, +1, +3, +0, +0.293023, +0.622011, +0.0849663, +57375, +2250},
            {+182090, +2.65267, +0.995702, +0.891156, +31, +0, +1, +1, +0, +0.29218, +0.623093, +0.0847273, +70125, +2750},
            {+182352, +2.65649, +0.997135, +0.891156, +31, +0, +1, +0, +0, +0.29176, +0.622455, +0.0857846, +84150, +3300},
            {+120270, +2.70142, +0.792768, +0.717687, +31, +51, +1, +147, +0, +0.427181, +0.486813, +0.0860065, +8400, +0},
            {+150654, +3.38389, +0.993046, +0.717687, +31, +51, +1, +3, +0, +0.341299, +0.588308, +0.0703931, +10800, +0},
            {+150865, +3.38863, +0.994437, +0.717687, +31, +51, +1, +2, +0, +0.340821, +0.588884, +0.0702946, +13500, +0},
            {+39680, +0.605469, +0.222063, +0.870748, +32, +5, +396, +146, +0, +0.654738, +0.294254, +0.0510081, +3243, +0},
            {+38656, +0.589844, +0.216332, +0.870748, +32, +5, +400, +146, +0, +0.672082, +0.27931, +0.0486082, +1081, +0},
            {+39835, +0.603113, +0.222063, +0.87415, +32, +4, +396, +146, +0, +0.655153, +0.293787, +0.0510606, +3384, +0},
            {+38807, +0.587549, +0.216332, +0.87415, +32, +4, +400, +146, +0, +0.672508, +0.278893, +0.0485995, +1128, +0},
            {+39990, +0.600775, +0.222063, +0.877551, +32, +3, +396, +146, +0, +0.655539, +0.293373, +0.0510878, +3528, +0},
            {+38958, +0.585271, +0.216332, +0.877551, +32, +3, +400, +146, +0, +0.672904, +0.278505, +0.0485908, +1176, +0},
            {+40145, +0.598456, +0.222063, +0.880952, +32, +2, +396, +146, +0, +0.655374, +0.293586, +0.05104, +3675, +0},
            {+39109, +0.583012, +0.216332, +0.880952, +32, +2, +400, +146, +0, +0.672735, +0.278759, +0.0485055, +1225, +0},
            {+40300, +0.596154, +0.222063, +0.884354, +32, +1, +396, +146, +0, +0.652854, +0.296154, +0.0509926, +3825, +0},
            {+39260, +0.580769, +0.216332, +0.884354, +32, +1, +400, +146, +0, +0.670148, +0.281457, +0.0483953, +1275, +0},
            {+40455, +0.59387, +0.222063, +0.887755, +32, +0, +396, +146, +0, +0.650352, +0.298702, +0.0509455, +3978, +153},
            {+39411, +0.578544, +0.216332, +0.887755, +32, +0, +400, +146, +0, +0.66758, +0.284134, +0.048286, +1326, +51},
            {+115564, +4.14371, +0.991404, +0.568027, +33, +93, +1, +4, +0, +0.429745, +0.554031, +0.0162248, +990, +0},
            {+115731, +4.1497, +0.992837, +0.568027, +33, +93, +1, +3, +0, +0.429124, +0.554674, +0.0162014, +1170, +0},
            {+116065, +4.16168, +0.995702, +0.568027, +33, +93, +1, +1, +0, +0.42789, +0.555956, +0.0161547, +1365, +0},
            {+116232, +4.16766, +0.997135, +0.568027, +33, +93, +1, +0, +0, +0.427275, +0.555544, +0.0171812, +1575, +0},
            {+118674, +4.05848, +0.994269, +0.581633, +33, +89, +0, +3, +0, +0.419292, +0.556575, +0.0241333, +1911, +0},
            {+119016, +4.07018, +0.997135, +0.581633, +33, +89, +0, +1, +0, +0.418087, +0.557849, +0.024064, +2205, +0},
            {+118332, +4.04678, +0.991404, +0.581633, +33, +89, +1, +4, +0, +0.420453, +0.556739, +0.0228087, +1386, +0},
            {+33790, +0.711009, +0.222063, +0.741497, +33, +42, +396, +146, +0, +0.736372, +0.215715, +0.0479136, +828, +0},
            {+33136, +0.697248, +0.217765, +0.741497, +33, +42, +399, +146, +0, +0.750905, +0.201231, +0.0478634, +276, +0},
            {+65880, +4.575, +0.786533, +0.408163, +33, +140, +1, +147, +0, +0.541166, +0.455055, +0.0037796, +45, +0},
            {+66360, +4.60833, +0.792264, +0.408163, +33, +140, +1, +143, +0, +0.537251, +0.458996, +0.00375226, +55, +0},
            {+135218, +3.59278, +0.998567, +0.659864, +33, +66, +0, +0, +0, +0.375216, +0.557995, +0.0667884, +11550, +0},
            {+135135, +3.55385, +0.992837, +0.663265, +33, +65, +0, +4, +0, +0.375484, +0.557457, +0.0670589, +6468, +0},
            {+135330, +3.55897, +0.994269, +0.663265, +33, +65, +0, +3, +0, +0.374943, +0.558095, +0.0669622, +8316, +0},
            {+135720, +3.56923, +0.997135, +0.663265, +33, +65, +0, +1, +0, +0.373865, +0.559365, +0.0667698, +10395, +0},
            {+135915, +3.57436, +0.998567, +0.663265, +33, +65, +0, +0, +0, +0.373329, +0.558893, +0.0677777, +12705, +0},
            {+142065, +3.38049, +0.992837, +0.697279, +33, +55, +0, +4, +0, +0.357998, +0.572006, +0.0699961, +7728, +0},
            {+117117, +4.10059, +0.992837, +0.57483, +30, +94, +0, +4, +0, +0.428213, +0.555837, +0.0159499, +280, +0},
            {+117286, +4.10651, +0.994269, +0.57483, +30, +94, +0, +3, +0, +0.427596, +0.556477, +0.0159269, +360, +0},
            {+117624, +4.11834, +0.997135, +0.57483, +30, +94, +0, +1, +0, +0.426367, +0.557752, +0.0158811, +450, +0},
            {+117793, +4.12426, +0.998567, +0.57483, +30, +94, +0, +0, +0, +0.425755, +0.557359, +0.0168856, +550, +0},
            {+43176, +1.52976, +0.368195, +0.571429, +30, +95, +396, +44, +0, +0.571822, +0.428178, +0, +36, +0},
            {+43344, +1.53571, +0.369628, +0.571429, +30, +95, +396, +43, +0, +0.569606, +0.430394, +0, +60, +0},
            {+42672, +1.5119, +0.363897, +0.571429, +30, +95, +399, +44, +0, +0.578576, +0.421424, +0, +18, +0},
            {+42840, +1.51786, +0.36533, +0.571429, +30, +95, +399, +43, +0, +0.576307, +0.423693, +0, +36, +0},
            {+42504, +1.50595, +0.362464, +0.571429, +30, +95, +400, +44, +0, +0.580863, +0.419137, +0, +6, +0},
            {+42672, +1.5119, +0.363897, +0.571429, +30, +95, +400, +43, +0, +0.578576, +0.421424, +0, +18, +0},
            {+33338, +0.748815, +0.226361, +0.717687, +29, +53, +396, +143, +0, +0.760244, +0.195453, +0.0443038, +1134, +0},
    };

    // match the labels to the training data
    float mainLabel = 1.0;
    float labels[nrSamples];
    // create the labels
    for (unsigned int i = 0; i < nrSamples; ++i) {
        if (i < nrMainClass) {
            labels[i] = mainLabel;
        } else {
            labels[i] = -mainLabel;
        }
    }

    // normalize the values above
    float maxFeatures[nrFeatures];

    for (unsigned int i = 0; i < nrFeatures; ++i) {
        maxFeatures[i] = numeric_limits<float>::min();
    }

    for (unsigned int i = 0; i < nrSamples; ++i) {
        for (unsigned int j = 0; j < nrFeatures; ++j) {
            if (maxFeatures[j] < trainingData[i][j]) {
                maxFeatures[j] = trainingData[i][j];
            }
        }
    }

    for (unsigned int i = 0; i < nrSamples; ++i) {
        for (unsigned int j = 0; j < nrFeatures; ++j) {
            if (excludedFeatures.find(j) != excludedFeatures.end()) {
                trainingData[i][j] = 0.0;
            } else {
                trainingData[i][j] /= maxFeatures[j];
            }
        }
    }

    // compute the max of the values of the candidates, but normalize later
    for (unsigned int i = 0; i < nrFeatures; ++i) {
        maxFeatures[i] = numeric_limits<float>::min();
    }

    for (vector<CandidateOpening>::iterator it = candidates.begin(); it != candidates.end(); ++it) {
        for (unsigned int feat = 0; feat < nrFeatures; ++feat) {
            if (maxFeatures[feat] < it->features[feat]) {
                maxFeatures[feat] = it->features[feat];
            }
        }
    }

    // create two OpenCV matrices
    cv::Mat trainingMat(nrSamples, nrFeatures, CV_32FC1, trainingData);
    cv::Mat labelsMat(nrSamples, 1, CV_32FC1, labels);

    // set up the SVM parameters
    cv::SVMParams params;
    params.svm_type = cv::SVM::NU_SVC;
    params.kernel_type = cv::SVM::RBF;
    params.term_crit = cv::TermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 1000, 1e-6);

    params.nu    = 0.8;
    params.C     = 8.0;
    params.gamma = 20;

    // create SVM and train it
    cv::SVM svm;
    svm.train_auto(trainingMat, labelsMat, cv::Mat(), cv::Mat(), params); // XXX with or without auto

    // store these openings, and filter some more later on
    std::vector<CandidateOpening> tempOpenings;

    for (vector<CandidateOpening>::iterator it = candidates.begin(); it != candidates.end(); ++it) {
        if (it->features.size() != nrFeatures) {
            throw runtime_error("number of features in candidate in does not equal the expected one");
        }

        float candidateData[nrFeatures];
        for (size_t jt = 0; jt < it->features.size(); ++jt) {
            if (excludedFeatures.find(jt) != excludedFeatures.end()) {
                candidateData[jt] = 0.0;
            } else {
                // also normalize the data here, and not in the candidateOpening object
                candidateData[jt] = static_cast<float>(it->features[jt]) / maxFeatures[jt];
            }
        }
        cv::Mat candidateMat(1, nrFeatures, CV_32FC1, candidateData);

        // decide of which type the current opening is
        float response = svm.predict(candidateMat);
        if (response == mainLabel) {
            tempOpenings.push_back(*it);
        }
    }

    // filter openings with respect to how much they overlap with the Canny edge
    cv::Mat canny, hSobel, vSobel, combined;
    surf.detectEdges(canny, hSobel, vSobel, combined);

    double maxEdgeCoverage = numeric_limits<double>::min();
    double averageArea = 0.0;
    for (vector<CandidateOpening>::iterator it = tempOpenings.begin(); it != tempOpenings.end(); ++it) {
        int y1 = it->edges[0];
        int y2 = it->edges[1];
        int x1 = it->edges[2];
        int x2 = it->edges[3];

        // how many pixels overlap?
        int total = 2 * (y2 - y1 + x2 - x1) + 1;
        int count = total;

        if (y2 < y1 || x2 < x1) {
            throw runtime_error("candidate edges must be in proper order");
        }

        for (int i = y1; i <= y2; ++i) {
            if (combined.data[i * combined.cols + x1] == 0) {
                count--;
            }
            if (combined.data[i * combined.cols + x2] == 0) {
                count--;
            }
        }

        for (int i = x1; i <= x2; ++i) {
            if (combined.data[y1 * combined.cols + i] == 0) {
                count--;
            }
            if (combined.data[y2 * combined.cols + i] == 0) {
                count--;
            }
        }

        // remember the edge coverage inside the object
        it->edgeCoverage = static_cast<double>(count) / total;
        if (maxEdgeCoverage < it->edgeCoverage) {
            maxEdgeCoverage = it->edgeCoverage;
        }

        // increment the average area
        averageArea += it->features[0];
    }
    averageArea /= tempOpenings.size();

    unsigned int discarded = 0;
    for (vector<CandidateOpening>::iterator it = tempOpenings.begin(); it != tempOpenings.end(); ++it) {
        // TODO put coef in class
        if ((it->features[0] > 0.3 * averageArea) &&
                (it->edgeCoverage > MIN_EDGE_COV * maxEdgeCoverage))
        {
            openings.push_back(*it);
        } else {
            discarded++;
        }
    }

    if (!quiet) cout << "** Discarded openings due to relative size to average candidate: " << discarded << endl;

    // draw the chosen rectangles, and also the occupancy map
    cv::Mat imgWithOpenings = surf.depthImg;
    cv::cvtColor(imgWithOpenings, imgWithOpenings, CV_GRAY2BGR, 3);

    for (size_t i = 0; i < openings.size(); ++i) {
        int y1 = openings[i].edges[0];
        int y2 = openings[i].edges[1];
        int x1 = openings[i].edges[2];
        int x2 = openings[i].edges[3];

        cv::rectangle(imgWithOpenings, cv::Point(x1, y1), cv::Point(x2, y2), cv::Scalar(0, 0, MAX_IMG_VAL), 1);
    }

    cv::imwrite("./img/depthCandidates.png", imgWithOpenings);

    if (!quiet) cout << "** Detected " << openings.size() << " potential openings" << endl;
}

void model::Scene::clusterOpenings(const LabeledPlane3d& surf, const vector<CandidateOpening>& openings,
        std::vector<CandidateOpening>& result) const
{
    // clear the result just in case
    result.clear();

    if (openings.size() == 1) {
        if (!quiet) cerr << "WARNING: only one candidate received for clustering!" << endl;
        result.push_back(openings.front());
        return;
    } else if (openings.size() < 1) {
        if (!quiet) cerr << "WARNING: no candidates received for clustering!" << endl;
        return;
    }

    if (!quiet) cout << endl << "== Clustering openings for surface centered at " << surf.pt << endl;

    size_t nrSamples = openings.size();
    unsigned int dim = 7;

    // a few matrix initializations
    CvMat* points   = cvCreateMat(nrSamples, dim, CV_32FC1);
    CvMat* clusters = cvCreateMat(nrSamples, 1, CV_32SC1);

    // fill in the matrices with data
    for (size_t i = 0; i < nrSamples; ++i) {
        if (openings[i].features.size() != model::CandidateOpening::NR_FEATURES) {
            throw runtime_error("invalid feature vector size");
        }

        // compute the center of each opening
        float x = static_cast<float>(openings[i].edges[2] + openings[i].edges[3]) / 2.0;
        float y = static_cast<float>(openings[i].edges[0] + openings[i].edges[1]) / 2.0;

        points->data.fl[i * dim + 0]  = x;                      // window center
        points->data.fl[i * dim + 1]  = y;                      // window center
        points->data.fl[i * dim + 2]  = openings[i].edges[0];   // upper edge
        points->data.fl[i * dim + 3]  = openings[i].edges[1];   // lower edge
        points->data.fl[i * dim + 4]  = openings[i].edges[2];   // left edge
        points->data.fl[i * dim + 5]  = openings[i].edges[3];   // right edge
        points->data.fl[i * dim + 6]  = openings[i].features[1];// edge ratio
    }

    // termination criteria for the kMeans algorithm
    CvTermCriteria term;
    term.type     = CV_TERMCRIT_ITER | CV_TERMCRIT_EPS;
    term.max_iter = 10;
    term.epsilon  = 1.0;
    int maxNrClusters = 256;

    int nrClusters = 2;                                             // from how many clusters to start
    double prev, compactness;                                       // previous compactness, and current compactness
    CvMat *centers = cvCreateMat(nrClusters - 1, dim, CV_32FC1);    // place where we compute the centers of each cluster
    cvKMeans2(points, nrClusters - 1, clusters, term, 1, 0, 0, centers, &prev);

    while (1) {
        cvReleaseMat(&centers);
        centers = cvCreateMat(nrClusters, dim, CV_32FC1);
        cvKMeans2(points, nrClusters, clusters, term, 1, 0, 0, centers, &compactness);

        // TODO put somewhere everything in class
        double deltaCompactness = compactness - prev;
        if (!quiet) cout << "** Delta compactness: " << deltaCompactness << endl;
        if (fabs(deltaCompactness) < 1.0 || nrClusters >= maxNrClusters) {
            break;
        }

        prev = compactness;
        nrClusters++;
    }

    if (!quiet) cout << "** Detected " << static_cast<unsigned int>(nrClusters) << " clusters of potential openings" << endl;

    // generate colors for each cluster
    int r, g, b;
    cv::Scalar colors[nrClusters];
    for (int i = 0; i < nrClusters; ++i) {
        randomColor(MAX_IMG_VAL, r, g, b);
        colors[i] = (cv::Scalar(b, g, r));
    }

    // create an image with the clusters in separate colors
    cv::Mat clusterImg = surf.depthImg;
    cv::Mat finalImg   = surf.depthImg;
    cv::cvtColor(clusterImg, clusterImg, CV_GRAY2BGR, 3);
    cv::cvtColor(finalImg, finalImg, CV_GRAY2BGR, 3);

    // count how many elements we have in each cluster
    vector<int> clusterCount;
    clusterCount.resize(nrClusters);
    fill(clusterCount.begin(), clusterCount.end(), 0);

    // compute the candidate for each cluster with the best coverage of empty area
    vector<CandidateOpening> bestCandidates;
    bestCandidates.resize(nrClusters);

    vector<double> bestEmptyAreas;
    bestEmptyAreas.resize(nrClusters);
    fill(bestEmptyAreas.begin(), bestEmptyAreas.end(), numeric_limits<double>::min());

    // fill in the result separating each cluster
    for (size_t i = 0; i < nrSamples; ++i) {
        // the cluster of the current opening
        int currCluster = clusters->data.i[i];

        // draw each cluster
        int y1 = openings[i].edges[0];
        int y2 = openings[i].edges[1];
        int x1 = openings[i].edges[2];
        int x2 = openings[i].edges[3];
        cv::rectangle(clusterImg, cv::Point(x1, y1), cv::Point(x2, y2), colors[currCluster], 1);

        // TODO avoid hard coding
        // find which is the best choice from the cluster
        double currEmptyArea = openings[i].features[0] *
                (openings[i].features[9] - openings[i].features[10] - openings[i].features[11]);
        if (bestEmptyAreas[currCluster] < currEmptyArea) {
            bestEmptyAreas[currCluster] = currEmptyArea;
            bestCandidates[currCluster] = openings[i];
        }

        clusterCount[currCluster]++;
    }

    // divide by the cluster count to get the average of the cluster
    for (size_t i = 0; i < bestEmptyAreas.size(); ++i) {
        if (clusterCount[i] <= 0) {
            continue;
        }

        int y1 = bestCandidates[i].edges[0];
        int y2 = bestCandidates[i].edges[1];
        int x1 = bestCandidates[i].edges[2];
        int x2 = bestCandidates[i].edges[3];

        cv::rectangle(finalImg, cv::Point(x1, y1), cv::Point(x2, y2), colors[i], 2);
        result.push_back(bestCandidates[i]);
    }

    if (!quiet) {
        cout << "** The count for each cluster is [";
        for (unsigned int i = 0; i < clusterCount.size() - 1; ++i) {
            cout << clusterCount[i] << ", ";
        }
        cout << clusterCount.back() << "]" << endl;
    }

    // display the center of the cluster only for the clusters that have candidates
    for (int i = 0; i < nrClusters; ++i) {
        if (clusterCount[i] > 0) {
            int x = static_cast<int>(centers->data.fl[i * dim + 0]);
            int y = static_cast<int>(centers->data.fl[i * dim + 1]);
            cv::circle(clusterImg, cv::Point(x, y), 2, colors[i], CV_FILLED);
            cv::circle(finalImg, cv::Point(x, y), 2, colors[i], CV_FILLED);
        }
    }

    cv::imwrite("./img/depthClusters.png", clusterImg);
    cv::imwrite("./img/depthOpenings.png", finalImg);

    cvReleaseMat(&points);
    cvReleaseMat(&centers);
    cvReleaseMat(&clusters);
}

void model::Scene::addFinalOpenings(const LabeledPlane3d& surf,
        std::vector<CandidateOpening>& result)
{
    int imgHeight = static_cast<int>(surf.depthMap.size());
    int imgWidth  = static_cast<int>(surf.depthMap.front().size());

    vector<CandidateOpening> openings, candidates;
    this->detectPotentialOpenings(surf, openings);
    this->clusterOpenings(surf, openings, candidates);

    // sweep across the wall and see which candidates overlap
    for (int i = 0; i < imgHeight; ++i) {
        for (int j = 0; j < imgWidth; ++j) {
            // put currently overlapping candidates here
            vector<pair<vector<CandidateOpening>::iterator, double> > temp;

            // the maximum opening area in the current overlapping windows
            double bestEmptyArea = numeric_limits<double>::min();

            for (vector<CandidateOpening>::iterator it = candidates.begin(); it < candidates.end(); ++it) {
                int y1 = it->edges[0];
                int y2 = it->edges[1];
                int x1 = it->edges[2];
                int x2 = it->edges[3];

                if (i >= y1 && i < y2 && j >= x1 && j <= x2) {
                    // TODO avoid hard coding
                    double freeArea = it->features[0] *
                            (it->features[9] - it->features[10] - it->features[11]);
                    temp.push_back(make_pair(it, freeArea));

                    if (bestEmptyArea < freeArea) {
                        bestEmptyArea = freeArea;
                    }
                }
            }

            for (vector<pair<vector<CandidateOpening>::iterator, double> >::iterator it = temp.begin(); it < temp.end(); ++it) {
                // if the area is smaller than the maximum discard this plane
                if (it->second < bestEmptyArea) {
                    candidates.erase(it->first);
                }
            }
        }
    }

    // display an image with the final openings
    int r, g, b;
    cv::Mat finalOpeningsImg = surf.depthImg.clone();
    cv::cvtColor(finalOpeningsImg, finalOpeningsImg, CV_GRAY2BGR, 3);

    for (vector<CandidateOpening>::iterator it = candidates.begin(); it < candidates.end(); ++it) {
        int y1 = it->edges[0];
        int y2 = it->edges[1];
        int x1 = it->edges[2];
        int x2 = it->edges[3];

        randomColor(MAX_IMG_VAL, r, g, b);
        cv::rectangle(finalOpeningsImg, cv::Point(x1, y1), cv::Point(x2, y2), cv::Scalar(b, g, r), 3);

        // add the new openings as windows
        Point3d pt(0.0, 0.0, 0.0);
        pt += surf.patches[y1].front().first;
        pt += surf.patches[y2].front().first;
        pt += surf.patches.front()[x1].first;
        pt += surf.patches.front()[x2].first;
        pt /= 4.0;

        vector<Point3d> hull;
        hull.push_back(surf.patches[y1][x1].first);
        hull.push_back(surf.patches[y1][x2].first);
        hull.push_back(surf.patches[y2][x2].first);
        hull.push_back(surf.patches[y2][x1].first);

        Plane3d toPush(pt, surf.normal, hull);
        this->finalOpenings.push_back(toPush);
    }

    cv::imwrite("./img/depthOpeningsFinal.png", finalOpeningsImg);
    if (!quiet) cout << "** Final openings found on current surface: " << candidates.size() << endl;
    if (!quiet) cout << "** Final openings found so far: " << this->finalOpenings.size() << endl;

    // copy the result
    result = candidates;
}

void model::Scene::correct(LabeledPlane3d& surf, const vector<CandidateOpening>& openings) {
    if (!quiet) cout << endl << "== Correcting surface centered at " << surf.pt << endl;

    int imgHeight = static_cast<int>(surf.depthMap.size());
    int imgWidth  = static_cast<int>(surf.depthMap.front().size());

    // some images we are interested in
    cv::Mat depthImg = surf.depthImg.clone();
    cv::Mat depthImgMask(imgHeight, imgWidth, CV_8UC1);
    cv::Mat labelsImg(imgHeight, imgWidth, CV_8UC3);

    // compute the maximum from the image
    double max = numeric_limits<double>::min();
    double mean = 0.0, stdDev = 0.0;
    int count = 0;

    for (int i = 0; i < imgHeight; ++i) {
        for (int j = 0; j < imgWidth; ++j) {
            // set all empty and occluded to unknown
            if (surf.patches[i][j].second == EMPTY || surf.patches[i][j].second == OCCLUDED) {
                surf.patches[i][j].second = UNKOWN;
            // compute the mean of the occupied patches
            } else if (surf.patches[i][j].second == OCCUPIED) {
                mean += surf.depthImg.data[i * surf.depthImg.cols + j];
                count++;
            }

            // compute the maximum from the image
            if (max < surf.depthMap[i][j]) {
                max = surf.depthMap[i][j];
            }
        }
    }

    // adjust the mean
    mean /= count;
    count = 0;

    // compute the standard deviation
    for (int i = 0; i < imgHeight; ++i) {
        for (int j = 0; j < imgWidth; ++j) {
            if (surf.patches[i][j].second == OCCUPIED) {
                stdDev += pow(surf.depthImg.data[i * surf.depthImg.cols + j] - mean, 2.0);
                count++;
            }

        }
    }

    // adjust the standard deviation
    stdDev = sqrt(stdDev / count);

    // mark all patches contained in openings
    for (vector<CandidateOpening>::const_iterator it = openings.begin(); it != openings.end(); ++it) {
        int y1 = it->edges[0];
        int y2 = it->edges[1];
        int x1 = it->edges[2];
        int x2 = it->edges[3];

        for (int i = 0; i < imgHeight; ++i) {
            for (int j = 0; j < imgWidth; ++j) {
                if (i >= y1 && i <= y2 && j >= x1 && j <= x2) {
                    surf.patches[i][j].second = OPENING;
                }
            }
        }
    }

    // adjust the image
    for (int i = 0; i < imgHeight; ++i) {
        for (int j = 0; j < imgWidth; ++j) {
            // initially mark as not part of the mask
            depthImgMask.data[i * depthImgMask.cols + j] = 0;

            // put the color into the image according to the label
            int curr = (i * 3 * labelsImg.cols) + 3 * j;
            switch (surf.patches[i][j].second) {
            case OPENING:
                labelsImg.data[curr + 0] = 200;
                labelsImg.data[curr + 1] = 200;
                labelsImg.data[curr + 2] = 200;
                break;
            case UNKOWN:
                labelsImg.data[curr + 0] = 50;
                labelsImg.data[curr + 1] = 50;
                labelsImg.data[curr + 2] = 50;

                // add pixel to mask for inpainting
                depthImgMask.data[i * depthImgMask.cols + j] = 200;
                break;
            case OCCUPIED:
                labelsImg.data[curr + 0] = 0;
                labelsImg.data[curr + 1] = 0;
                labelsImg.data[curr + 2] = 200;
                break;
            default:
                throw runtime_error("invalid default branch taken, possibly EMPTY or OCCLUDED patches");
                break;
            }
        }
    }

    cv::imwrite("./img/labelsWithOpenings.png", labelsImg);
    cv::imwrite("./img/depthMask.png", depthImgMask);
    cv::imwrite("./img/depthSimple.png", depthImg);

    // TODO add values as part of class
    if (!quiet) cout << "** Filling in missing data using inpaint algorithm" << endl;
    cv::inpaint(depthImg, depthImgMask, depthImg, 0.05 * (imgWidth + imgHeight), cv::INPAINT_NS);
    cv::imwrite("./img/depthInpaint.png", depthImg);

    // add some gaussian noise to make the image look more real
    if (!quiet) cout << "** Adding gaussian noise: mean = " << mean << ", stdDev = " << stdDev << endl;
    cv::Mat noise(imgHeight, imgWidth, CV_8UC1);
    cv::randn(noise, mean, stdDev);
    cv::addWeighted(depthImg, 0.9, noise, 0.1, 0.0, depthImg);
    cv::GaussianBlur(depthImg, depthImg, cv::Size(3, 3), 0.5, 0.5);
    cv::imwrite("./img/depthInpaintNoise.png", depthImg);
    surf.correctedDepthImg = depthImg.clone();

    // TODO apply the inverse transform from 2D to 3D and create a new point cloud using UOS RGB
}

void model::Scene::writeModel(string dir) {
    if (!fileIsDir(dir)) {
        throw runtime_error(dir + " is not a directory");
    }

    string outputDir;

    if (dir[dir.length()-1] == '/') {
        outputDir = dir + "model";
    }
    else {
        outputDir = dir + "/model";
    }

    int ret = mkdir(outputDir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);

    if (ret != 0 && errno != EEXIST) {
        throw runtime_error("unable to create directory " + outputDir);
    }

    if (!quiet) cout << endl << "== Writing planes to " << outputDir << " directory..." << endl;

    string planesListFile = outputDir + "/planes.list";
    ofstream list(planesListFile.c_str(), ios::out);

    for (unsigned int counter = 0; counter < this->walls.size() + this->finalOpenings.size() + 2; ++counter) {
        Plane3d currPlane; // the plane to be printed
        string type;       // the objet's type, floor, ceiling, wall or opening

        if (counter < this->walls.size()) {
            currPlane = this->walls[counter];
            type = "Wall";
        }
        else if (counter == this->walls.size()) {
            currPlane = this->ceiling;
            type = "Ceiling";
        }
        else if (counter == this->walls.size() + 1) {
            currPlane = this->floor;
            type = "Floor";
        } else {
            // TODO find a better solution
            currPlane = this->finalOpenings[counter - (this->walls.size() + 2)];
            currPlane.normal.normalize();
            for (vector<Point3d>::iterator it = currPlane.hull.begin(); it != currPlane.hull.end(); ++it) {
                it->x += currPlane.normal.x;
                it->y += currPlane.normal.y;
                it->z += currPlane.normal.z;
            }
            type = "Opening";
        }

        stringstream ss;
        ss << outputDir << "/scan" << setfill('0') << setw(3) << counter << ".3d";
        ofstream file(ss.str().c_str(), ios::out);


        // add the plane to the list of planes
        list << type << " " << ss.str() << endl;
        boost::algorithm::to_lower(type);
        if (!quiet) cout << "** Writing " << type << ss.str() << endl;

        // add the points to the scan file
        for (vector<Point3d>::iterator it = currPlane.hull.begin();
                it != currPlane.hull.end(); ++it)
        {
            file << *it << endl;
        }

        file.close();
    }

    list.close();
}

void model::Scene::writeCorrectedWalls(std::string dir) {
    if (!fileIsDir(dir)) {
        throw runtime_error(dir + " is not a directory");
    }

    string outputDir;

    if (dir[dir.length()-1] == '/') {
        outputDir = dir + "cloud";
    }
    else {
        outputDir = dir + "/cloud";
    }

    int ret = mkdir(outputDir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);

    if (ret != 0 && errno != EEXIST) {
        throw runtime_error("unable to create directory " + outputDir);
    }

    if (!quiet) cout << endl << "== Writing corrected points to " << outputDir << " directory..." << endl;

    // write the pose file
    string poseFileName = outputDir + "/scan000.pose";
    ofstream out(poseFileName.c_str(), ios::out);
    out << "0 0 0" << endl;
    out << "0 0 0" << endl;
    out.close();

    string pointsFileName = outputDir + "/scan000.3d";
    out.open(pointsFileName.c_str(), ios::out);

    // fetch each points and its color
    vector<pair<Point3d, cv::Vec3i> > points;

    getCorrectedWall(this->ceiling, points);
    for (vector<pair<Point3d, cv::Vec3i> >::iterator it = points.begin(); it != points.end(); ++it) {
        out << it->first << " " << static_cast<int>(it->second[0]) << " " << static_cast<int>(it->second[1]) << " " << static_cast<int>(it->second[2]) << endl;
    }

    getCorrectedWall(this->floor, points);
    for (vector<pair<Point3d, cv::Vec3i> >::iterator it = points.begin(); it != points.end(); ++it) {
        out << it->first << " " << static_cast<int>(it->second[0]) << " " << static_cast<int>(it->second[1]) << " " << static_cast<int>(it->second[2]) << endl;
    }

    for (vector<LabeledPlane3d>::iterator wall = this->walls.begin(); wall != this->walls.end(); ++wall) {
        getCorrectedWall(*wall, points);
        for (vector<pair<Point3d, cv::Vec3i> >::iterator it = points.begin(); it != points.end(); ++it) {
            out << it->first << " " << static_cast<int>(it->second[0]) << " " << static_cast<int>(it->second[1]) << " " << static_cast<int>(it->second[2]) << endl;
        }
    }

    out.close();
}

void model::Scene::getCorrectedWall(const LabeledPlane3d& surf, vector<pair<Point3d, cv::Vec3i> >& points) {
    if (!quiet) cout << "** Computing point cloud for surface centered at " << surf.pt << endl;

    // clear the output
    points.clear();

    if (surf.correctedDepthImg.empty()) {
        if (!quiet) cout << "WARNING: the surface centered at " << surf.pt << " has not been corrected yet" << endl;
        return;
    }

    if ((size_t) surf.correctedDepthImg.rows != surf.patches.size() ||
            (size_t) surf.correctedDepthImg.cols != surf.patches.front().size())
    {
        throw runtime_error("corrected image must be the same size as depth map");
    }

    // compute the maximum of the depth image
    int depthImgMax = 0;
    for (size_t i = 0; i < surf.depthMap.size(); ++i) {
        for (size_t j = 0; j < surf.depthMap.front().size(); ++j) {
            if (depthImgMax < surf.depthMap[i][j]) {
                depthImgMax = surf.depthMap[i][j];
            }
        }
    }

    // make the original points darker than the corrected ones
    int r = 255, g = 50, b = 50;
    int wr = 255, wg = 150, wb = 150;

    for (int i = 0; i < surf.correctedDepthImg.rows; ++i) {
        for (int j = 0; j < surf.correctedDepthImg.cols; ++j) {
            cv::Vec3i color;

            // only consider occupied and unknown pixels
            if (surf.patches[i][j].second == OCCUPIED) {
                color = cv::Vec3i(r, g, b);
            } else if (surf.patches[i][j].second == UNKOWN) {
                color = cv::Vec3i(wr, wg, wb);
            } else {
                continue;
            }

            // the value from the depth map
            double depth = static_cast<double>(depthImgMax * surf.correctedDepthImg.data[i * surf.correctedDepthImg.cols + j]) / MAX_IMG_VAL;

            // the distance from where the raytracing took place to the point it hit
            double dist = depth + surf.depthMapDistances.first - surf.depthMapDistances.second;

            Vector3d normal = surf.normal;
            normal.normalize();

            Point3d pt = surf.patches[i][j].first;
            pt.x = pt.x + normal.x * dist;
            pt.y = pt.y + normal.y * dist;
            pt.z = pt.z + normal.z * dist;

            points.push_back(make_pair(pt, color));
        }
    }
}

void model::Scene::flood(LabeledPlane3d& surf,
        const int& i, const int& j,
        const Label& target, const Label& replacement)
{
    // no point of carrying on
    if (target == replacement) {
        return;
    }

    pair<int, int> north = make_pair(i-1, j);
    pair<int, int> south = make_pair(i+1, j);
    pair<int, int> east = make_pair(i, j+1);
    pair<int, int> west = make_pair(i, j-1);

    vector<pair<int, int> > neigh;
    neigh.push_back(north);
    neigh.push_back(south);
    neigh.push_back(east);
    neigh.push_back(west);

    if (surf.patches[i][j].second == target) {
        surf.patches[i][j].second = replacement;
    }

    for (vector<pair<int, int> >::iterator it = neigh.begin(); it != neigh.end(); ++it) {
        if ((it->first > 0 && it->second > 0) &&
                surf.patches[it->first][it->second].second == target)
        {
            flood(surf, it->first, it->second, target, replacement);
        }
    }
}

bool model::Scene::isOccupied(const Point3d& center, const double& width) {
    // look for a close point
    double pt[] = {center.x, center.y, center.z};
    double* closest = this->octTree->FindClosest(pt, pow(sqrt(3) * (width / 2.0), 2.0), 0);

    // bounding box limits
    double xUppLim = center.x + width / 2.0;
    double xLowLim = center.x - width / 2.0;
    double yUppLim = center.y + width / 2.0;
    double yLowLim = center.y - width / 2.0;
    double zUppLim = center.z + width / 2.0;
    double zLowLim = center.z - width / 2.0;

    if (closest != NULL &&
            (closest[0] > xLowLim && closest[0] < xUppLim) &&
            (closest[1] > yLowLim && closest[1] < yUppLim) &&
            (closest[2] > zLowLim && closest[2] < zUppLim))
    {
        return true;
    }

    return false;
}
