/*
* SemiGlobalMatcher.h
*
* Copyright (c) 2014-2015 SEACAVE
*
* Author(s):
*
*      cDc <cdc.seacave@gmail.com>
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
*
* Additional Terms:
*
*      You are required to preserve legal notices and author attributions in
*      that material or in the Appropriate Legal Notices displayed by works
*      containing it.
*/

#ifndef _MVS_SEMIGLOBALMATCHER_H_
#define _MVS_SEMIGLOBALMATCHER_H_


// I N C L U D E S /////////////////////////////////////////////////

#include "DepthMap.h"


// D E F I N E S ///////////////////////////////////////////////////

// similarity method
#define SGM_SIMILARITY_WZNCC 1
#define SGM_SIMILARITY_CENSUS 2
#define SGM_SIMILARITY SGM_SIMILARITY_WZNCC


// S T R U C T S ///////////////////////////////////////////////////

namespace MVS {

class Scene;

namespace STEREO {

// An implementation of the popular Semi-Global Matching (SGM) algorithm.
class MVS_API SemiGlobalMatcher
{
public:
	typedef uint8_t  Mask;      // used to mark the valid region to be estimated
	typedef int16_t  Disparity; // contains the allowable disparity range
	typedef uint8_t  Cost;      // used to describe a single disparity cost
	typedef uint16_t AccumCost; // used to accumulate CostType values
	typedef uint64_t Index; // index pointing to pixel costs/accumulated-costs

	enum : Mask      { INVALID = 0, VALID = DECLARE_NO_INDEX(Mask) }; // invalid/valid mask value
	enum : Disparity { NO_DISP = DECLARE_NO_INDEX(Disparity) }; // invalid disparity value
	enum : AccumCost { NO_ACCUMCOST = DECLARE_NO_INDEX(AccumCost) }; // invalid accumulated cost value
	enum : Index     { NO_INDEX = DECLARE_NO_INDEX(Index) }; // invalid index value

	struct Range {
		Disparity minDisp;
		Disparity maxDisp;
		Disparity avgDisp() const { return (minDisp+maxDisp)>>1; }
		Disparity numDisp() const { return maxDisp-minDisp; }
		Disparity isValid() const { return minDisp<maxDisp; }
	};
	struct PixelData {
		Index idx; // index to pixel costs/accumulated-costs data
		Range range; // first and last disparity evaluated for this pixel
	};

	typedef TImage<Mask> MaskMap; // image of accumulated disparity cost
	typedef TImage<Disparity> DisparityMap; // disparity image type
	typedef TImage<AccumCost> AccumCostMap; // image of accumulated disparity cost
	typedef CLISTDEF0IDX(PixelData,Index) PixelMap; // map of pixel data
	typedef CLISTDEF0IDX(Cost,Index) CostsMap; // map of pre-computed disparity costs
	typedef CLISTDEF0IDX(AccumCost,Index) AccumCostsMap; // map of accumulated disparity costs

	enum : int { numDirs = 4 }; // 2 or 4 directions accumulated per pixel pass
	#if SGM_SIMILARITY == SGM_SIMILARITY_CENSUS
	enum : int { halfWindowSizeX = 3, halfWindowSizeY = 4 }; // patch kernel size
	#else
	enum : int { halfWindowSizeX = 3, halfWindowSizeY = 3 }; // patch kernel size
	#endif
	enum : int { windowSizeX = halfWindowSizeX*2+1, windowSizeY = halfWindowSizeY*2+1, numTexels = windowSizeX*windowSizeY }; // patch kernel info

	#if SGM_SIMILARITY == SGM_SIMILARITY_CENSUS
	typedef uint64_t Census; // used to store Census transform
	typedef TImage<Census> CensusMap; // image of Census transforms
	typedef Image8U ImageGray; // used to store intensities image
	STATIC_ASSERT(sizeof(Census)*8 >= numTexels);
	#else
	typedef WeightedPatchFix<numTexels> WeightedPatch; // pre-computed patch weights
	typedef Image32F ImageGray; // used to store normalized float intensities image
	#endif


	// Available sub-pixel options
	enum SgmSubpixelMode {
		SUBPIXEL_NA = 0, // no sub-pixel processing
		SUBPIXEL_LINEAR,
		SUBPIXEL_POLY4,
		SUBPIXEL_PARABOLA,
		SUBPIXEL_SINE,
		SUBPIXEL_COSINE,
		SUBPIXEL_LC_BLEND // probably the best option
	};

	struct ViewData {
		Image8U3 imageColor; // color image
		ImageGray imageGray; // intensity image
		#if SGM_SIMILARITY == SGM_SIMILARITY_CENSUS
		CensusMap imageCensus; // image of Census transforms (valid per scale)
		#endif
		ViewData GetImage(REAL scale) const {
			if (ISEQUAL(scale, REAL(1))) {
				#if SGM_SIMILARITY == SGM_SIMILARITY_CENSUS
				CensusTransform(imageGray, const_cast<CensusMap&>(imageCensus));
				#endif
				return *this;
			}
			ASSERT(scale < 1);
			ViewData scaledView;
			cv::resize(imageColor, scaledView.imageColor, cv::Size(), scale, scale, cv::INTER_AREA);
			cv::resize(imageGray, scaledView.imageGray, cv::Size(), scale, scale, cv::INTER_AREA);
			#if SGM_SIMILARITY == SGM_SIMILARITY_CENSUS
			CensusTransform(scaledView.imageGray, scaledView.imageCensus);
			#endif
			return scaledView;
		}
	};

	typedef Point2f DepthRange;
	typedef TImage<DepthRange> DepthRangeMap;

public:
	SemiGlobalMatcher(SgmSubpixelMode subpixelMode=SUBPIXEL_LC_BLEND, Disparity subpixelSteps=4, AccumCost P1=3, AccumCost P2=4, float P2alpha=14, float P2beta=38);
	~SemiGlobalMatcher();

	void Match(const Scene& scene, IIndex idxImage, IIndex numNeighbors, unsigned minResolution=320);
	void Fuse(const Scene& scene, IIndex idxImage, IIndex numNeighbors, unsigned minViews, DepthMap& depthMap, ConfidenceMap& confMap);

	static void CreateThreads(unsigned nMaxThreads=1);
	static void DestroyThreads();
	static void* ThreadWorker(void*);
	static void WaitThreadWorkers(unsigned nJobs);

	static bool ExportDisparityDataRaw(const String& fileName, const DisparityMap&, const AccumCostMap&, const cv::Size& imageSize, const Matrix3x3& H, const Matrix4x4& Q, Disparity subpixelSteps);
	static bool ExportDisparityDataRawFull(const String& fileName, const DisparityMap&, const AccumCostMap&, const cv::Size& imageSize, const Matrix3x3& H, const Matrix4x4& Q, Disparity subpixelSteps);
	static bool ImportDisparityDataRaw(const String& fileName, DisparityMap&, AccumCostMap&, cv::Size& imageSize, Matrix3x3& H, Matrix4x4& Q, Disparity& subpixelSteps);
	static bool ImportDisparityDataRawFull(const String& fileName, DisparityMap&, AccumCostMap&, cv::Size& imageSize, Matrix3x3& H, Matrix4x4& Q, Disparity& subpixelSteps);
	static Image8U3 DisparityMap2Image(const DisparityMap&, Disparity minDisparity=NO_DISP, Disparity maxDisparity=NO_DISP);
	static bool ExportDisparityMap(const String& fileName, const DisparityMap&, Disparity minDisparity=NO_DISP, Disparity maxDisparity=NO_DISP);
	static bool ExportPointCloud(const String& fileName, const Image&, const DisparityMap&, const Matrix4x4& Q, Disparity subpixelSteps);
	static bool ImportPointCloud(const String& fileName, const ImageArr& images, PointCloud&);

protected:
	void Match(const ViewData& leftImage, const ViewData& rightImage, DisparityMap& disparityMap, AccumCostMap& costMap);
	Index Disparity2RangeMap(const DisparityMap& disparityMap, const MaskMap& maskMap, Disparity minNumDisp=3, Disparity minNumDispInvalid=16);
	#if SGM_SIMILARITY == SGM_SIMILARITY_CENSUS
	static void CensusTransform(const Image8U& imageGray, CensusMap& imageCensus);
	#endif
	static void ConsistencyCrossCheck(DisparityMap& l2r, const DisparityMap& r2l, Disparity thCross=1);
	static void FilterByCost(DisparityMap&, const AccumCostMap&, AccumCost th);
	static void ExtractMask(const DisparityMap&, MaskMap&, int thValid=3);
	static void FlipDirection(const DisparityMap& l2r, DisparityMap& r2l);
	static void UpscaleMask(MaskMap& maskMap, const cv::Size& size2x);
	void RefineDisparityMap(DisparityMap& disparityMap) const;
	#ifndef _RELEASE
	void DisplayState(const cv::Size& size) const;
	#endif

	static CLISTDEF0IDX(AccumCost,int) GenerateP2s(AccumCost P2, float P2alpha, float P2beta);
	static void Depth2DisparityMap(const DepthMap&, const Matrix3x3& invH, const Matrix4x4& invQ, Disparity subpixelSteps, DisparityMap&);
	static void Disparity2DepthMap(const DisparityMap&, const AccumCostMap&, const Matrix3x3& H, const Matrix4x4& Q, Disparity subpixelSteps, DepthMap&, ConfidenceMap&);
	static bool ProjectDisparity2DepthMap(const DisparityMap&, const AccumCostMap&, const Matrix4x4& Q, Disparity subpixelSteps, DepthMap&, DepthRangeMap&, ConfidenceMap&);

protected:
	// parameters
	SgmSubpixelMode subpixelMode;
	Disparity subpixelSteps;
	AccumCost P1;
	CLISTDEF0IDX(AccumCost,int) P2s;

	// main memory buffers that must be allocated
	PixelMap imagePixels;
	CostsMap imageCosts;
	AccumCostsMap imageAccumCosts;
	Disparity maxNumDisp; // maximum number of disparities per-pixel

	// multi-threading
	static EventThreadPool threads; // worker threads
	static Semaphore sem; // signal job end
};
/*----------------------------------------------------------------*/


MVS_API bool ExportCamerasEngin(const Scene&, const String& fileName);
/*----------------------------------------------------------------*/

} // namespace STEREO

} // namespace MVS

#endif // _MVS_SEMIGLOBALMATCHER_H_
