#include "ANSANOMALIB.h"
namespace ANSCENTER {
	bool ANSANOMALIB::OptimizeModel(bool fp16, std::string& optimizedModelFolder) {
		std::lock_guard<std::recursive_mutex> lock(_mutex);
		if (!ANSODBase::OptimizeModel(fp16, optimizedModelFolder)) {
			return false;
		}
		if (FileExist(_modelFilePath)) {
			std::string modelName = GetFileNameWithoutExtension(_modelFilePath);
			std::string binaryModelName = modelName + ".bin";
			std::string modelFolder = GetParentFolder(_modelFilePath);
			std::string optimizedModelPath = CreateFilePath(modelFolder, binaryModelName);
			if (FileExist(optimizedModelPath)) {
				this->_logger.LogDebug("ANSANOMALIB::OptimizeModel", "This model is optimized. No need other optimization.", __FILE__, __LINE__);
				optimizedModelFolder = modelFolder;
				return true;
			}
			else {
				this->_logger.LogFatal("ANSANOMALIB::OptimizeModel", "This model can not be optimized.", __FILE__, __LINE__);
				optimizedModelFolder = modelFolder;
				return false;
			}
		}
		else {
			this->_logger.LogFatal("ANSANOMALIB::OptimizeModel", "This model is not exist. Please check the model path again.", __FILE__, __LINE__);
			optimizedModelFolder = "";
			return false;
		}
	}
	bool ANSANOMALIB::LoadModel(const std::string& modelZipFilePath, const std::string& modelZipPassword) {
		std::lock_guard<std::recursive_mutex> lock(_mutex);
		ModelLoadingGuard mlg(_modelLoading);
		try {
			bool result = ANSODBase::LoadModel(modelZipFilePath, modelZipPassword);
			if (!result) return false;
			_device = "AUTO:GPU,CPU";
			_openvinoPreprocess = true;
			_efficientAd = false;
			// 0. Check if the configuration file exist
			if (FileExist(_modelConfigFile)) {
				ModelType modelType;
				std::vector<int> inputShape;
				_classes = ANSUtilityHelper::GetConfigFileContent(_modelConfigFile, modelType, inputShape);
				if (inputShape.size() == 2) {
					if (inputShape[0] > 0)_modelConfig.inpHeight = inputShape[0];
					if (inputShape[1] > 0)_modelConfig.inpWidth = inputShape[1];
				}
			}
			else {// This is old version of model zip file
				std::string xmlfile = CreateFilePath(_modelFolder, "model.xml");
				std::string metafile = CreateFilePath(_modelFolder, "metadata.json");
				std::string binaryfile = CreateFilePath(_modelFolder, "model.bin");
				if (std::filesystem::exists(xmlfile)) {
					_modelFilePath = xmlfile;
					_metaFilePath = metafile;
					this->_logger.LogDebug("ANSANOMALIB::Initialize. Loading OpenVINO weight", _modelFilePath, __FILE__, __LINE__);
					if (FileExist(_metaFilePath)) {
						this->_logger.LogDebug("ANSANOMALIB::Initialize. Loading meta data", _metaFilePath, __FILE__, __LINE__);
						if (FileExist(binaryfile)) {
							this->_logger.LogDebug("ANSANOMALIB::Initialize. Loading binary model", binaryfile, __FILE__, __LINE__);
						}
						else {
							this->_logger.LogError("ANSANOMALIB::Initialize. Binary model file does not exit", binaryfile, __FILE__, __LINE__);
							return false;
						}
					}
					else {
						this->_logger.LogError("ANSANOMALIB::Initialize. Meta data file does not exist", _metaFilePath, __FILE__, __LINE__);
						return false;
					}
				}
				else {
					this->_logger.LogError("ANSANOMALIB::Initialize. Model file does not exist", _modelFilePath, __FILE__, __LINE__);
					return false;
				}
			}
			// Load Model from Here
			InitModel(_modelFilePath, _metaFilePath, _device, _openvinoPreprocess, _efficientAd);
			_isInitialized = true;
			return true;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::LoadModel", e.what(), __FILE__, __LINE__);
			return false;
		}
	}

	bool ANSANOMALIB::LoadModelFromFolder(std::string licenseKey, ModelConfig modelConfig, std::string modelName, std::string className, const std::string& modelFolder, std::string& labelMap) {
		std::lock_guard<std::recursive_mutex> lock(_mutex);
		ModelLoadingGuard mlg(_modelLoading);
		try {
			bool result = ANSODBase::LoadModelFromFolder(licenseKey, modelConfig, modelName, className, modelFolder, labelMap);
			if (!result) return false;
			std::string _modelName = modelName;
			if (_modelName.empty()) {
				_modelName = "model";
			}
			std::string xmlfileName = _modelName + ".xml";
			std::string binfileName = _modelName + ".bin";
			_modelConfig = modelConfig;
			_modelConfig.detectionType = ANSCENTER::DetectionType::SEGMENTATION;
			_modelConfig.modelType = ModelType::ANOMALIB;
			_modelConfig.inpHeight = 640;
			_modelConfig.inpWidth = 640;
			if (_modelConfig.modelMNSThreshold < 0.2)
				_modelConfig.modelMNSThreshold = 0.5;
			if (_modelConfig.modelConfThreshold < 0.2)
				_modelConfig.modelConfThreshold = 0.5;
			_openvinoPreprocess = true;
			_efficientAd = false;
			_device = "AUTO:GPU,CPU";
			// 0. Check if the configuration file exist
			if (FileExist(_modelConfigFile)) {
				ModelType modelType;
				std::vector<int> inputShape;
				_classes = ANSUtilityHelper::GetConfigFileContent(_modelConfigFile, modelType, inputShape);
				if (inputShape.size() == 2) {
					if (inputShape[0] > 0)_modelConfig.inpHeight = inputShape[0];
					if (inputShape[1] > 0)_modelConfig.inpWidth = inputShape[1];
				}
			}
			else {// This is old version of model zip file
				std::string xmlfile = CreateFilePath(_modelFolder, xmlfileName);
				std::string metafile = CreateFilePath(_modelFolder, "metadata.json");
				std::string binaryfile = CreateFilePath(_modelFolder, binfileName);

				if (std::filesystem::exists(xmlfile)) {
					_modelFilePath = xmlfile;
					_metaFilePath = metafile;
					this->_logger.LogDebug("ANSANOMALIB::Initialize. Loading OpenVINO weight", _modelFilePath, __FILE__, __LINE__);
					if (FileExist(_metaFilePath)) {
						this->_logger.LogDebug("ANSANOMALIB::Initialize. Loading meta data", _metaFilePath, __FILE__, __LINE__);
						if (FileExist(binaryfile)) {
							this->_logger.LogDebug("ANSANOMALIB::Initialize. Loading binary model", binaryfile, __FILE__, __LINE__);
						}
						else {
							this->_logger.LogError("ANSANOMALIB::Initialize. Binary model file does not exit", binaryfile, __FILE__, __LINE__);
							return false;
						}
					}
					else {
						this->_logger.LogError("ANSANOMALIB::Initialize. Meta data file does not exist", _metaFilePath, __FILE__, __LINE__);
						return false;
					}
				}
				else {
					this->_logger.LogError("ANSANOMALIB::Initialize. Model file does not exist", _modelFilePath, __FILE__, __LINE__);
					return false;
				}
			}
			// Load Model from Here
			InitModel(_modelFilePath, _metaFilePath, _device, _openvinoPreprocess, _efficientAd);
			labelMap = "Normal,Defect";
			_isInitialized = true;
			return true;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::LoadModel", e.what(), __FILE__, __LINE__);
			return false;
		}
	}
	bool ANSANOMALIB::Initialize(std::string licenseKey, ModelConfig modelConfig, const std::string& modelZipFilePath, const std::string& modelZipPassword, std::string& labelMap) {
		std::lock_guard<std::recursive_mutex> lock(_mutex);
		ModelLoadingGuard mlg(_modelLoading);
		try {
			std::string openVINOVersion = ov::get_openvino_version().buildNumber;
			//this->_logger.LogDebug("ANSANOMALIB::Initialize. OpenVINO version", openVINOVersion, __FILE__, __LINE__);
			bool result = ANSODBase::Initialize(licenseKey, modelConfig, modelZipFilePath, modelZipPassword, labelMap);
			if (!result) return false;
			// Parsing for ABNORMAL only here
			_modelConfig = modelConfig;
			_modelConfig.detectionType = ANSCENTER::DetectionType::SEGMENTATION;
			_modelConfig.modelType = ModelType::ANOMALIB;
			_modelConfig.inpHeight = 640;
			_modelConfig.inpWidth = 640;
			if (_modelConfig.modelMNSThreshold < 0.2)
				_modelConfig.modelMNSThreshold = 0.5;
			if (_modelConfig.modelConfThreshold < 0.2)
				_modelConfig.modelConfThreshold = 0.5;
			_openvinoPreprocess = true;
			_efficientAd = false;
			_device = "AUTO:GPU,CPU";
			// 0. Check if the configuration file exist
			if (FileExist(_modelConfigFile)) {
				ModelType modelType;
				std::vector<int> inputShape;
				_classes = ANSUtilityHelper::GetConfigFileContent(_modelConfigFile, modelType, inputShape);
				if (inputShape.size() == 2) {
					if (inputShape[0] > 0)_modelConfig.inpHeight = inputShape[0];
					if (inputShape[1] > 0)_modelConfig.inpWidth = inputShape[1];
				}
			}
			else {// This is old version of model zip file
				std::string xmlfile = CreateFilePath(_modelFolder, "model.xml");
				std::string metafile = CreateFilePath(_modelFolder, "metadata.json");
				std::string binaryfile = CreateFilePath(_modelFolder, "model.bin");

				if (std::filesystem::exists(xmlfile)) {
					_modelFilePath = xmlfile;
					_metaFilePath = metafile;
					this->_logger.LogDebug("ANSANOMALIB::Initialize. Loading OpenVINO weight", _modelFilePath, __FILE__, __LINE__);
					if (FileExist(_metaFilePath)) {
						this->_logger.LogDebug("ANSANOMALIB::Initialize. Loading meta data", _metaFilePath, __FILE__, __LINE__);
						if (FileExist(binaryfile)) {
							this->_logger.LogDebug("ANSANOMALIB::Initialize. Loading binary model", binaryfile, __FILE__, __LINE__);
						}
						else {
							this->_logger.LogError("ANSANOMALIB::Initialize. Binary model file does not exit", binaryfile, __FILE__, __LINE__);
							return false;
						}
					}
					else {
						this->_logger.LogError("ANSANOMALIB::Initialize. Meta data file does not exist", _metaFilePath, __FILE__, __LINE__);
						return false;
					}
				}
				else {
					this->_logger.LogError("ANSANOMALIB::Initialize. Model file does not exist", _modelFilePath, __FILE__, __LINE__);
					return false;
				}
			}
			// Load Model from Here
			InitModel(_modelFilePath, _metaFilePath, _device, _openvinoPreprocess, _efficientAd);
			labelMap = "Normal,Defect";
			_isInitialized = true;
			return true;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::Initialize", e.what(), __FILE__, __LINE__);
			labelMap = "Normal,Defect";
			return false;
		}
	}
	std::vector<Object> ANSANOMALIB::RunInference(const cv::Mat& input) {
		return RunInference(input, "ANSANOMALIBCAM");
	}
	std::vector<Object> ANSANOMALIB::RunInference(const cv::Mat& input, const std::string& camera_id) {
		if (!PreInferenceCheck("ANSANOMALIB::RunInference")) return {};
		std::vector<Object> output;
		output.clear();
		if (!_licenseValid) {
			this->_logger.LogError("ANSANOMALIB::RunInference", "Invalid License", __FILE__, __LINE__);
			return output;
		}
		if (!_isInitialized) {
			this->_logger.LogError("ANSANOMALIB::RunInference", "Model is not initialized", __FILE__, __LINE__);
			return output;
		}
		try {
			if (input.empty()) return output;
			if ((input.cols < 10) || (input.rows < 10)) return output;
			cv::Mat frame = input;
			Object result = InferenceSingleImage(frame);
			result.cameraId = camera_id;
			output.push_back(result);
			//EnqueueDetection(output, camera_id);
			return output;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::RunInference", e.what(), __FILE__, __LINE__);
			return output;
		}
	}
	

	ANSANOMALIB::~ANSANOMALIB() {
		try {
			//this->_logger.LogDebug("ANSANOMALIB::~ANSANOMALIB()", "Release ANSANOMALIB ", __FILE__, __LINE__);
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::~ANSANOMALIB()", e.what(), __FILE__, __LINE__);
		}
	}
	bool ANSANOMALIB::Destroy() {
		try {
			//this->_logger.LogDebug("ANSANOMALIB::Destroy", "Release ANSANOMALIB ", __FILE__, __LINE__);
			return true;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::Destroy", e.what(), __FILE__, __LINE__);
			return false;
		}

	}
	// Private
	void ANSANOMALIB::InitModel(std::string& model_path,
		std::string& meta_path,
		std::string& device,
		bool openvino_preprocess,
		bool efficient_ad) {
		try {
			this->_efficientAd = efficient_ad;
			this->_openvinoPreprocess = openvino_preprocess;
			this->_meta = ANSUtilityHelper::GetJson(meta_path);
			this->GetModel(model_path, device);
			this->_inputs = this->_compiledModel.inputs();
			this->_outputs = this->_compiledModel.outputs();
			for (auto input : this->_inputs) {
				std::cout << "Input: " << input.get_any_name() << ": [ ";
				for (auto j : input.get_shape()) {
					std::cout << j << " ";
				}
				std::cout << "] ";
				std::cout << "dtype: " << input.get_element_type() << std::endl;
			}

			for (auto output : this->_outputs) {
				std::cout << "Output: " << output.get_any_name() << ": [ ";
				for (auto j : output.get_shape()) {
					std::cout << j << " ";
				}
				std::cout << "] ";
				std::cout << "dtype: " << output.get_element_type() << std::endl;
			}
			this->_inferRequest = this->_compiledModel.create_infer_request();
			//this->WarmUp();
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::InitModel", e.what(), __FILE__, __LINE__);
		}
	}

	void ANSANOMALIB::GetModel(std::string& model_path, std::string& device) {
		try {
			// Step 1. Initialize OpenVINO Runtime core
			ov::Core core;
			// Step 2. Read a Model from a Drive
			std::shared_ptr<ov::Model> model = core.read_model(model_path);

			if (this->_openvinoPreprocess) {
				std::vector<float> mean;
				std::vector<float> std;
				if (!this->_efficientAd) {
					mean = { this->_meta._mean[0] * 255, this->_meta._mean[1] * 255, this->_meta._mean[2] * 255 };
					std = { this->_meta._std[0] * 255, this->_meta._std[1] * 255, this->_meta._std[2] * 255 };
				}
				else {
					mean = { 0., 0., 0. };
					std = { 255., 255., 255. };
				}

				// Step 3. Inizialize Preprocessing for the model
				// https://mp.weixin.qq.com/s/4lkDJC95at2tK_Zd62aJxw
				// https://blog.csdn.net/sandmangu/article/details/107181289
				// https://docs.openvino.ai/latest/openvino_2_0_preprocessing.html
				ov::preprocess::PrePostProcessor ppp = ov::preprocess::PrePostProcessor(model);

				// Specify input image format
				ppp.input(0).tensor()
					.set_color_format(ov::preprocess::ColorFormat::RGB)     // BGR -> RGB
					.set_element_type(ov::element::u8)
					.set_layout(ov::Layout("HWC"));                         // HWC NHWC NCHW

				// Specify preprocess pipeline to input image without resizing
				ppp.input(0).preprocess()
					//  .convert_color(ov::preprocess::ColorFormat::RGB)
					.convert_element_type(ov::element::f32)
					.mean(mean)
					.scale(std);

				// Specify model's input layout
				ppp.input(0).model().set_layout(ov::Layout("NCHW"));

				// Specify output results format
				for (size_t i = 0; i < model->outputs().size(); i++) {
					ppp.output(i).tensor().set_element_type(ov::element::f32);
				}

				// Embed above steps in the graph
				model = ppp.build();
			}
			// Step 4. Load the Model to the Device
			this->_compiledModel = core.compile_model(model, device,
				ov::hint::performance_mode(ov::hint::PerformanceMode::THROUGHPUT),
				ov::inference_num_threads(4));

		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::GetModel", e.what(), __FILE__, __LINE__);
		}
	}
	void ANSANOMALIB::WarmUp() {
		try {
			cv::Size size = cv::Size(this->_meta.inferSize[1], this->_meta.inferSize[0]);
			cv::Scalar color = cv::Scalar(0, 0, 0);
			cv::Mat input = cv::Mat(size, CV_8UC3, color);
			this->Inference(input);
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::WarmUp", e.what(), __FILE__, __LINE__);
		}
	}

	Object ANSANOMALIB::Inference(cv::Mat& image) {
		try {
			Object result;
			cv::Mat blob;
			if (image.empty())return result;
			if ((image.cols <= 5) || (image.rows <= 5))return result;
			cv::Mat processedImage;
			if (image.channels() == 1) {
				cv::cvtColor(image, processedImage, cv::COLOR_GRAY2BGR);
			}
			else {
				processedImage = image;
			}

			this->_meta.imageSize[0] = processedImage.size().height;
			this->_meta.imageSize[1] = processedImage.size().width;
			cv::cvtColor(processedImage, processedImage, cv::ColorConversionCodes::COLOR_BGR2RGB);    // BGR2RGB
			if (this->_openvinoPreprocess) {
				cv::resize(processedImage, blob, { this->_meta.inferSize[0], this->_meta.inferSize[1] });
			}
			else {
				blob = PreProcess(processedImage, this->_meta, this->_efficientAd);
				blob = cv::dnn::blobFromImage(blob); // [H, W, C] -> [N, C, H, W]

			}

			ov::Tensor input_tensor = ov::Tensor(this->_compiledModel.input(0).get_element_type(),
				this->_compiledModel.input(0).get_shape(), (float*)blob.data);

			this->_inferRequest.set_input_tensor(input_tensor);
			this->_inferRequest.infer();

			ov::Tensor result1;
			result1 = this->_inferRequest.get_output_tensor(0);
			cv::Mat anomaly_map = cv::Mat(cv::Size(this->_meta.inferSize[1], this->_meta.inferSize[0]), CV_32FC1, result1.data<float>());
			cv::Mat pred_score;

			if (this->_outputs.size() == 2) {
				ov::Tensor result2 = this->_inferRequest.get_output_tensor(1);
				pred_score = cv::Mat(cv::Size(1, 1), CV_32FC1, result2.data<float>());
			}
			else {
				double _, maxValue;
				cv::minMaxLoc(anomaly_map, &_, &maxValue);
				pred_score = cv::Mat(cv::Size(1, 1), CV_32FC1, maxValue);
			}
			float preScore = pred_score.at<float>(0, 0);

			std::vector<cv::Mat> post_mat = PostProcess(anomaly_map, pred_score, this->_meta);
			anomaly_map = post_mat[0];
			float rawScore = post_mat[1].at<float>(0, 0);
			float score = rawScore;

			if (preScore >= this->_meta.imageThreshold) {
				result.classId = 1;
				result.className = "Defect";
			}
			else {
				result.classId = 0;
				score = 1 - rawScore;
				result.className = "Normal";
			}
			result.box.x = 5;
			result.box.y = 5;
			result.box.width = this->_meta.imageSize[0] - 5;
			result.box.height = this->_meta.imageSize[1] - 5;

			result.polygon = ANSUtilityHelper::RectToNormalizedPolygon(result.box, processedImage.cols, processedImage.rows);
			result.confidence = score;
			result.mask = anomaly_map;
			result.extraInfo = "";
			return result;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::Inference", e.what(), __FILE__, __LINE__);
			Object result;
			return result;
		}

	}

	Object ANSANOMALIB::InferenceSingleImage(cv::Mat& image) {
		try {
			Object result = this->Inference(image);
			std::vector<cv::Mat> images = GenImages(image, result.mask, result.confidence);
			cv::applyColorMap(images[0], images[0], cv::ColormapTypes::COLORMAP_JET);
			cv::Mat res;
			cv::hconcat(images, res);
			result.mask = res;
			return result;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::InferenceSingleImage", e.what(), __FILE__, __LINE__);
			Object result;
			return result;
		}

	}

	void ANSANOMALIB::InferenceMultiImages(std::string& image_dir, std::string& save_dir) {
		try {
			std::vector<cv::String> paths = GetImagePaths(image_dir);
			for (auto& image_path : paths) {
				cv::Mat image = ReadImage(image_path);
				Object result = this->Inference(image);
				std::vector<cv::Mat> images = GenImages(image, result.mask, result.confidence);
				cv::applyColorMap(images[0], images[0], cv::ColormapTypes::COLORMAP_JET);
				cv::Mat res;
				cv::hconcat(images, res);
				SaveScoreAndImages(result.confidence, res, image_path, save_dir);
			}
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::InferenceMultiImages", e.what(), __FILE__, __LINE__);
		}

	}

	std::vector<cv::String> ANSANOMALIB::GetImagePaths(std::string& path) {
		try {
			std::vector<cv::String> paths;
			cv::glob(path, paths, false);
			return paths;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::GetImagePaths", e.what(), __FILE__, __LINE__);
			std::vector<cv::String> paths;
			paths.clear();
			return paths;
		}

	}
	cv::Mat ANSANOMALIB::ReadImage(std::string& path) {
		try {
			auto image = cv::imread(path, cv::ImreadModes::IMREAD_COLOR);
			cv::cvtColor(image, image, cv::ColorConversionCodes::COLOR_BGR2RGB);
			return image;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::ReadImage", e.what(), __FILE__, __LINE__);
			cv::Mat result;
			return result;
		}

	}

	void ANSANOMALIB::SaveScoreAndImages(float score, cv::Mat image,
		cv::String& image_path, std::string& save_dir) {
		try {
			auto start = image_path.rfind('\\');
			if (start < 0 || start > image_path.length()) {
				start = image_path.rfind('/');
			}
			auto end = image_path.substr(start + 1).rfind('.');
			auto image_name = image_path.substr(start + 1).substr(0, end);
			std::ofstream ofs;
			ofs.open(save_dir + "/" + image_name + ".txt", std::ios::out);
			ofs << score;
			ofs.close();
			cv::imwrite(save_dir + "/" + image_name + ".jpg", image);
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::SaveScoreAndImages", e.what(), __FILE__, __LINE__);
		}
	}

	cv::Mat ANSANOMALIB::PreProcess(cv::Mat& image, MetaData& meta, bool efficient_ad) {
		try {
			std::vector<float> mean = { 0.485, 0.456, 0.406 };
			std::vector<float> std = { 0.229, 0.224, 0.225 };
			cv::Mat resized_image = ANSUtilityHelper::Resize(image, meta.inferSize[0], meta.inferSize[1], "bilinear");
			resized_image.convertTo(resized_image, CV_32FC3, 1.0 / 255, 0);
			if (!efficient_ad) {
				resized_image = ANSUtilityHelper::Normalize(resized_image, mean, std);
			}
			return resized_image;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::PreProcess", e.what(), __FILE__, __LINE__);
			cv::Mat result;
			return result;
		}

	}

	cv::Mat ANSANOMALIB::NormalizeMinMax(cv::Mat& targets, float threshold, float min_val, float max_val) {
		try {
			cv::Mat normalized = ((targets - threshold) / (max_val - min_val)) + 0.5;
			cv::threshold(normalized, normalized, 1, 1, cv::ThresholdTypes::THRESH_TRUNC);
			cv::threshold(normalized, normalized, 0, 0, cv::ThresholdTypes::THRESH_TOZERO);
			return normalized;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::NormalizeMinMax", e.what(), __FILE__, __LINE__);
			cv::Mat result;
			return result;
		}
	}

	std::vector<cv::Mat> ANSANOMALIB::PostProcess(cv::Mat& anomaly_map, cv::Mat& pred_score, MetaData& meta) {
		try {
			anomaly_map = NormalizeMinMax(anomaly_map, meta.pixelThreshold, meta.min, meta.max);
			pred_score = NormalizeMinMax(pred_score, meta.imageThreshold, meta.min, meta.max);
			anomaly_map = ANSUtilityHelper::Resize(anomaly_map, meta.imageSize[0], meta.imageSize[1], "bilinear");
			return std::vector<cv::Mat>{anomaly_map, pred_score};
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::PostProcess", e.what(), __FILE__, __LINE__);
			std::vector<cv::Mat> result;
			result.clear();
			return result;
		}

	}
	cv::Mat ANSANOMALIB::SuperimposeAnomalyMap(cv::Mat& anomaly_map, cv::Mat& origin_image) {
		try {
			auto anomaly = anomaly_map.clone();
			double minValue, maxValue;
			cv::minMaxLoc(anomaly, &minValue, &maxValue);
			anomaly = (anomaly - minValue) / (maxValue - minValue);
			anomaly.convertTo(anomaly, CV_8UC1, 255, 0);
			cv::applyColorMap(anomaly, anomaly, cv::ColormapTypes::COLORMAP_JET);
			cv::Mat combine;
			cv::addWeighted(anomaly, 0.4, origin_image, 0.6, 0, combine);
			return combine;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::SuperimposeAnomalyMap", e.what(), __FILE__, __LINE__);
			cv::Mat result;
			return result;
		}

	}

	cv::Mat ANSANOMALIB::AddLabel(cv::Mat& mixed_image, float score, int font) {
		try {
			std::string text = "Confidence Score " + std::to_string(score);
			int font_size = mixed_image.cols / 1024 + 1;
			int baseline = 0;
			int thickness = font_size / 2;
			cv::Size textsize = cv::getTextSize(text, font, font_size, thickness, &baseline);

			cv::rectangle(mixed_image, cv::Point(0, 0), cv::Point(textsize.width + 10, textsize.height + 10),
				cv::Scalar(225, 252, 134), cv::FILLED);

			cv::putText(mixed_image, text, cv::Point(0, textsize.height + 10), font, font_size,
				cv::Scalar(0, 0, 0), thickness);
			return mixed_image;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::AddLabel", e.what(), __FILE__, __LINE__);
			cv::Mat result;
			return result;
		}
	}
	cv::Mat ANSANOMALIB::ComputeMask(cv::Mat& anomaly_map, float threshold, int kernel_size) {
		try {
			cv::Mat mask = anomaly_map.clone();
			cv::threshold(mask, mask, threshold, 1, cv::ThresholdTypes::THRESH_BINARY);
			auto kernel = cv::getStructuringElement(cv::MorphShapes::MORPH_ELLIPSE, { kernel_size, kernel_size }, { -1, -1 });
			cv::morphologyEx(mask, mask, cv::MorphTypes::MORPH_OPEN, kernel, { -1, -1 }, 1);
			mask.convertTo(mask, CV_8UC1, 255, 0);
			return mask;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::ComputeMask", e.what(), __FILE__, __LINE__);
			cv::Mat result;
			return result;
		}
	}

	cv::Mat ANSANOMALIB::GenMaskBorder(cv::Mat& mask, cv::Mat& image) {
		try {
			cv::Mat b = cv::Mat::zeros(mask.size[0], mask.size[1], CV_8UC1);
			cv::Mat g = b.clone();
			cv::Mat r = b.clone();
			cv::Canny(mask, r, 128, 255, 3, false);
			std::vector<cv::Mat> rgb{ b, g, r };
			cv::Mat border;
			cv::merge(rgb, border);
			cv::addWeighted(border, 0.4, image, 0.6, 0, border);
			return border;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::GenMaskBorder", e.what(), __FILE__, __LINE__);
			cv::Mat result;
			return result;
		}
	}
	std::vector<cv::Mat> ANSANOMALIB::GenImages(cv::Mat& image, cv::Mat& anomaly_map, float score, float threshold) {
		try {
			cv::cvtColor(image, image, cv::ColorConversionCodes::COLOR_RGB2BGR);
			cv::Mat mask = ComputeMask(anomaly_map, threshold);
			cv::Mat border = GenMaskBorder(mask, image);
			cv::Mat superimposed_map = SuperimposeAnomalyMap(anomaly_map, image);
			superimposed_map = AddLabel(superimposed_map, score);
			return std::vector<cv::Mat>{mask, border, superimposed_map};
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::GenImages", e.what(), __FILE__, __LINE__);
			cv::Mat result;
			return result;
		}
	}
}
