#!D:/IDE/anaconda3/envs python
# -*- coding: UTF-8 -*-
"""
 @Author: Administrator
 @FileName：ImageAlgorithm.py\n
 @DateTime: 2025/7/24 13:46\n
 @Author  ：xsh
"""
import cv2
import numpy as np
from AppUtils import AppUtils
from AppCore.AppDBModels.ConfigModels.ImageAnalysisModel import ImageAnalysisModel
class ImageAlgorithm:
    @staticmethod
    def image_resize(image, w: int = -1, h: int = -1):
        if image is None or w is None or h is None:
            return None
        try:
            src_w: int = image.shape[1]
            src_h: int = image.shape[0]
            dst_w: int
            dst_h: int
            if w <= 0:
                dst_w = src_w
            else:
                dst_w = w
            if h <= 0:
                dst_h = src_h
            else:
                dst_h = h
            img = cv2.resize(image, dsize=(dst_w, dst_h), interpolation=cv2.INTER_LINEAR)
            return img
        except Exception as e:
            AppUtils.print_Exception(e)
            return None


    @classmethod
    def ProcessIntegrationImages(cls, images: list[np.ndarray], parm: ImageAnalysisModel, isGaussi: int = 1):
        image: np.ndarray
        contrast_weight     : float
        saturation_weight   : float
        exposure_weight     : float
        best_illumination   : float
        best_sigma          : float
        layers_num          : int
        eps                     = 1e-12
        parameter: ImageAnalysisModel
        if isinstance(parm, ImageAnalysisModel):
            parameter = parm
        else:
            parameter = ImageAnalysisModel.first()

        contrast_weight     = parameter.contrast_weight
        saturation_weight   = parameter.saturation_weight
        exposure_weight     = parameter.exposure_weight
        best_illumination   = parameter.best_illumination
        best_sigma          = parameter.best_sigma
        layers_num          = parameter.layers_num

        try:
            if isGaussi == 1:
                image = ImageAlgorithm.exposure_fusion_gaussi(images, (contrast_weight, saturation_weight, exposure_weight), best_illumination, best_sigma, eps)
            elif isGaussi == 0:
                image = ImageAlgorithm.exposure_fusion_lappyr(images, (contrast_weight, saturation_weight, exposure_weight), best_illumination, best_sigma, eps, layers_num)
            else:
                merge_mertens = cv2.createMergeMertens(contrast_weight=contrast_weight, saturation_weight=saturation_weight, exposure_weight=exposure_weight)
                res_mertens = merge_mertens.process(images)
                image = np.clip(res_mertens * 255, 0, 255).astype('uint8')

            return image
        except Exception as e:
            AppUtils.print_Exception(e)
            try:
                merge_mertens = cv2.createMergeMertens(contrast_weight=contrast_weight, saturation_weight=saturation_weight, exposure_weight=exposure_weight)
                res_mertens = merge_mertens.process(images)
                image = np.clip(res_mertens * 255, 0, 255).astype('uint8')
            except Exception as cvEx:
                AppUtils.print_Exception(cvEx)
                image = None
            return image


    @classmethod
    def ProcessMicronucleus(cls, src: np.ndarray, parameter: ImageAnalysisModel):
        image: np.ndarray = None
        try:
            isAnalysis    : int = parameter.isAnalysis
            if not isAnalysis:
                return src
            isGaussi      : int = parameter.isGaussi
            analysis_ksize: int = parameter.analysis_ksize  #
            analysis_sigma: int = parameter.analysis_sigma  #
            if isGaussi:
                image = cv2.GaussianBlur(src, (analysis_ksize, analysis_ksize), analysis_sigma, analysis_sigma)
            else:
                image = cv2.medianBlur(src, analysis_sigma)
        except Exception as e:
            AppUtils.print_Exception(e)
            image = None

        return image

    @staticmethod
    def zmMinFilterGray(src, r=7):
        '''最小值滤波，r是滤波器半径'''
        return cv2.erode(src, np.ones((2 * r + 1, 2 * r + 1)))

    @staticmethod
    def guidedfilter(I, p, r, eps):
        height, width = I.shape
        m_I = cv2.boxFilter(I, -1, (r, r))
        m_p = cv2.boxFilter(p, -1, (r, r))
        m_Ip = cv2.boxFilter(I * p, -1, (r, r))
        cov_Ip = m_Ip - m_I * m_p

        m_II = cv2.boxFilter(I * I, -1, (r, r))
        var_I = m_II - m_I * m_I

        a = cov_Ip / (var_I + eps)
        b = m_p - a * m_I

        m_a = cv2.boxFilter(a, -1, (r, r))
        m_b = cv2.boxFilter(b, -1, (r, r))
        return m_a * I + m_b

    @staticmethod
    def Defog(m, r, eps, w, maxV1):  # 输入rgb图像，值范围[0,1]
        '''计算大气遮罩图像V1和光照值A, V1 = 1-t/A'''
        V1 = np.min(m, 2)  # 得到暗通道图像
        Dark_Channel = ImageAlgorithm.zmMinFilterGray(V1, 7)
        # cv2.imshow('20190708_Dark',Dark_Channel)    # 查看暗通道
        # cv2.waitKey(0)
        # cv2.destroyAllWindows()

        V1 = ImageAlgorithm.guidedfilter(V1, Dark_Channel, r, eps)  # 使用引导滤波优化
        bins = 2000
        ht = np.histogram(V1, bins)  # 计算大气光照A
        d = np.cumsum(ht[0]) / float(V1.size)
        for lmax in range(bins - 1, 0, -1):
            if d[lmax] <= 0.999:
                break
        A = np.mean(m, 2)[V1 >= ht[1][lmax]].max()
        V1 = np.minimum(V1 * w, maxV1)  # 对值范围进行限制
        return V1, A

    @staticmethod
    def deHaze(m, r=8, eps=0.001, w=0.2, maxV1=0.80, bGamma=False):
        Y = np.zeros(m.shape)
        Mask_img, A = ImageAlgorithm.Defog(m, r, eps, w, maxV1)  # 得到遮罩图像和大气光照

        for k in range(3):
            Y[:, :, k] = (m[:, :, k] - Mask_img) / (1 - Mask_img / A)  # 颜色校正
        Y = np.clip(Y, 0, 1)
        if bGamma:
            Y = Y ** (np.log(0.5) / np.log(Y.mean()))  # gamma校正,默认不进行该操作
        return Y

    @staticmethod
    def high_reserve(img, ksize, sigm):
        img = img * 1.0
        gauss_out = cv2.GaussianBlur(img, (ksize, ksize), sigm)
        img_out = img - gauss_out + 128
        img_out = img_out / 255.0
        # 饱和处理
        mask_1 = img_out < 0
        mask_2 = img_out > 1
        img_out = img_out * (1 - mask_1)
        img_out = img_out * (1 - mask_2) + mask_2
        return img_out

    @staticmethod
    def usm(img, number):
        if isinstance(number, float):
            if number == 0.0:
                number = 0.1
        elif isinstance(number, int):
            if number == 0:
                number = 0.1
        blur_img = cv2.GaussianBlur(img, (0, 0), number)
        usm = cv2.addWeighted(img, 1.5, blur_img, -0.5, 0)

        return usm

    @staticmethod
    def Overlay(target, blend):
        mask = blend < 0.5
        img = 2 * target * blend * mask + (1 - mask) * (1 - 2 * (1 - target) * (1 - blend))
        return img

    @staticmethod
    def Saturation(rgb_img: np.ndarray, increment: float):
        # 将输入图像转换为浮点数，并归一化到0-1范围
        img = rgb_img.astype(np.float64) / 255.0
        # 计算每个像素的最小值，即HSL颜色空间中的亮度最低的部分
        img_min = img.min(axis=2)
        # 计算每个像素的最大值，即HSL颜色空间中的亮度最高的部分
        img_max = img.max(axis=2)

        # 计算饱和度（delta）和亮度（value）的中间值
        delta = (img_max - img_min)
        value = (img_max + img_min)
        # 计算HSL颜色空间中的亮度L
        L = value / 2.0

        # 根据亮度L计算饱和度s的两个可能值
        s1 = delta / (value + 1e-8)  # 防止除以0的情况，添加一个很小的数
        s2 = delta / (2 - value + 1e-8)
        # 根据亮度L选择饱和度s的正确值
        s = np.where(L < 0.5, s1, s2)

        # 计算增量调整后的饱和度，如果饱和度加上增量大于1，则保持原饱和度，否则用1减去增量
        temp = increment + s
        alpha = np.where(temp > 1, s, 1 - increment)
        # 计算调整后的alpha值
        alpha = 1 / alpha - 1

        # 对RGB图像的每个通道进行调整，以改变饱和度
        for i in range(3):
            img[:, :, i] += (img[:, :, i] - L) * alpha
        # 确保调整后的RGB值在0到1之间，如果超出这个范围，则将其限制在这个范围内
        img = np.clip(img, 0, 1)
        img = (img * 255).astype(np.uint8)
        # 返回调整后的图像
        return img

    @staticmethod
    def shadow(input_image, light):
        result: np.ndarray = None
        try:
            # 将输入图像转换为浮点类型并归一化，便于后续处理
            f = input_image.astype(np.float32) / 255.0
            # 使用OpenCV将归一化的图像转换为灰度图
            gray = cv2.cvtColor(f, cv2.COLOR_BGR2GRAY)

            # 计算灰度图的平方，用以确定阴影区域
            thresh = (1.0 - gray) ** 2
            # 计算阴影区域的平均值，用作后续的阈值
            t = np.mean(thresh)
            # 创建一个掩码，标记阴影区域（高于平均值的区域）
            mask = np.where(thresh >= t, 255, 0).astype(np.uint8)

            # 设置校正参数
            max_val = 4  # 最大校正范围
            bright = light / 100.0 / max_val  # 计算校正强度
            mid = 1.0 + max_val * bright  # 计算中间值

            # 创建两个过渡矩阵，用于调整图像的亮度和对比度
            midrate = np.where(mask == 255, mid, ((mid - 1.0) / t * thresh) + 1.0)
            brightrate = np.where(mask == 255, bright, (1.0 / t * thresh) * bright)

            # 使用指数函数调整图像亮度，并通过掩码实现平滑过渡
            result = np.clip(pow(f, 1.0 / midrate[:, :, np.newaxis]), 0.0, 1.0)
            # 根据计算出的亮度率调整图像，限制值在0到1之间
            result = np.clip(result * (1.0 / (1 - brightrate[:, :, np.newaxis])), 0.0, 1.0) * 255
            # 将结果转换为8位无符号整型，以便显示和保存
            result = result.astype(np.uint8)
        except Exception as e:
            AppUtils.print_Exception(e)
        return result

    @staticmethod
    def processImage(im: np.ndarray, parm: ImageAnalysisModel, flag: bool = False) -> np.ndarray:

        parameter: ImageAnalysisModel

        try:
            if isinstance(parm, ImageAnalysisModel):
                parameter = parm
            else:
                parameter = ImageAnalysisModel.first()
            analysis_ksize      : int       = parameter.analysis_ksize #
            analysis_sigma      : int       = parameter.analysis_sigma #
            analysis_radius     : int       = parameter.analysis_radius#
            analysis_weight     : float     = parameter.analysis_weight#

            _image: np.ndarray = im
            if flag:
                _image = (ImageAlgorithm.deHaze(im / 255.0, r=analysis_radius, w=analysis_weight) * 255).astype(np.uint8)
            # high = ImageAlgorithm.high_reserve(_image, analysis_ksize, analysis_sigma)
            usm1 = ImageAlgorithm.usm(_image, analysis_sigma)
            # result = (ImageAlgorithm.Overlay(_image / 255, usm1) * 255).astype(np.uint8)
            return usm1.astype(np.uint8)
        except Exception as e:
            AppUtils.print_Exception(e)
            return None

    @staticmethod
    def ProcessImages(IntegrationImages: list[np.ndarray], parm: ImageAnalysisModel, isAnalysis: bool = True) -> list[np.ndarray]:
        RIntegrationImages: list[np.ndarray] = []
        _image_save     : np.ndarray
        _image_process  : np.ndarray
        _image          : np.ndarray
        _index          : int
        __image         : np.ndarray
        for _index, _image in enumerate(IntegrationImages):
            try:
                if len(_image.shape) == 3:
                    _image_process = _image.copy() if not isAnalysis else ImageAlgorithm.processImage(_image.copy(), parm)
                elif len(_image.shape) == 2:
                    if isAnalysis:
                        __image = ImageAlgorithm.processImage(_image.copy(), parm)
                        _image_process = np.stack((__image.copy(),) * 3, axis=-1)
                    else:
                        _image_process = np.stack((_image.copy(),) * 3, axis=-1)
                else:
                    _image_process = None

            except Exception as e:
                AppUtils.print_Exception(e)
                _image_process = None

            if _image_process is not None:
                RIntegrationImages.append(_image_process)
        return RIntegrationImages

    @staticmethod
    def exposure_fusion_Preprocessing(images, alphas=(1.0, 1.0, 1.0), best_illumination=0.5, sigma=0.2, eps=1e-12) -> (np.ndarray, list):
        # 转化成 float 数据
        try:
            sequence = np.stack([it.astype("float32") / 255 for it in images], axis=0)
            S = len(sequence)
            H, W, C = sequence[0].shape
            # 准备一些中间变量
            laplace_kernel = np.array(([0, 1, 0], [1, -4, 1], [0, 1, 0]), dtype="float32")
            mse = lambda l, r: (l - r) * (l - r)
            best_illumination = np.full((H, W), best_illumination, dtype='float32')
            # 存放每张图像的权重图
            weights = []
            # 每张图像都求一个初始权重
            for s in range(S):
                # 从拉普拉斯求对比度
                gray = cv2.cvtColor(sequence[s], cv2.COLOR_BGR2GRAY)
                contrast = cv2.filter2D(gray, -1, laplace_kernel, borderType=cv2.BORDER_REPLICATE)
                contrast = np.abs(contrast)
                # 求饱和度
                mean = np.mean(sequence[s], axis=-1)
                saturation = np.sqrt(np.mean([mse(sequence[s][:, :, ch], mean) for ch in range(3)], axis=0))
                # 求亮度
                illumination = [np.exp(-0.5 * mse(sequence[s][:, :, ch], best_illumination) / (sigma * sigma)) for ch in range(3)]
                illumination = np.prod(illumination, axis=0)
                # 三者加权
                cur_weight = np.power(contrast, alphas[0]) * np.power(saturation, alphas[1]) * np.power(illumination, alphas[2])
                weights.append(cur_weight)
            # 归一化
            normalize = lambda x: x / np.expand_dims(np.sum(x, axis=0), axis=0)
            weights = np.stack(weights, axis=0)
            weights += eps
            weights = normalize(weights)
            sequence *= 255
        except Exception as e:
            AppUtils.print_Exception(e)
            sequence = None
            weights  = None
        return sequence, weights

    @staticmethod
    def exposure_fusion_lappyr(images, alphas=(1.0, 1.0, 1.0), best_illumination=0.5, sigma=0.2, eps=1e-12, layers_num=5):
        start: np.ndarray = None
        sequence, weights = ImageAlgorithm.exposure_fusion_Preprocessing(images, alphas, best_illumination, sigma, eps)
        if sequence is None or weights is None:
            return start
        try:
            S = len(sequence)
            H, W, C = sequence[0].shape
            # 求每张图的权重的高斯金字塔
            sequence_weights_pyramids = [ImageAlgorithm.build_gaussi_pyramid(weights[s], layers_num) for s in range(S)]
            # 求每张图的高斯金字塔, 以求 laplace
            sequence_gaussi_pyramids = [ImageAlgorithm.build_gaussi_pyramid(sequence[s], layers_num) for s in range(S)]
            # 求每张图的 laplace 金字塔

            sequence_laplace_pyramids = [ImageAlgorithm.build_laplace_pyramaid(sequence_gaussi_pyramids[s], layers_num) for s in range(S)]
            # 这里可以归一化
            # 每一个尺度, 融合一系列图像的的 laplace 细节, 得到一个融合的 laplace 金字塔
            fused_laplace_pyramid = [ np.sum([sequence_laplace_pyramids[k][n] * np.atleast_3d(sequence_weights_pyramids[k][n]) for k in range(S)], axis=0) for n in range(layers_num)]
            # 先从最底层的图像开始, 每次上采样都加上同等尺度的 laplace 细节
            start = fused_laplace_pyramid[layers_num - 1]
            for i in range(layers_num - 2, -1, -1):
                upsampled = cv2.resize(start, (fused_laplace_pyramid[i].shape[1], fused_laplace_pyramid[i].shape[0]))
                start = fused_laplace_pyramid[i] + upsampled
            # 灰度值截断在 0-255 之间
            start = np.clip(start, 0, 255).astype("uint8")
        except Exception as e:
            AppUtils.print_Exception(e)
        return start

    @staticmethod
    def exposure_fusion_gaussi(images, alphas=(1.0, 1.0, 1.0), best_illumination=0.5, sigma=0.2, eps=1e-12):
        smoothed_fusion = None
        sequence, weights = ImageAlgorithm.exposure_fusion_Preprocessing(images, alphas, best_illumination, sigma, eps)
        if sequence is None or weights is None:
            return smoothed_fusion
        try:
            normalize = lambda x: x / np.expand_dims(np.sum(x, axis=0), axis=0)
            smoothed_weights = np.stack([cv2.GaussianBlur(w, (101, 101), 17) for w in weights], axis=0)
            smoothed_weights = normalize(smoothed_weights)
            smoothed_fusion = np.sum(np.stack([smoothed_weights, smoothed_weights, smoothed_weights], axis=-1) * sequence, axis=0)
            smoothed_fusion = np.clip(smoothed_fusion, 0, 255).astype("uint8")
        except Exception as e:
            AppUtils.print_Exception(e)
            pass

        return smoothed_fusion




    # 根据最高分辨率的图像 high_res, 得到高度 layers 的高斯金字塔
    @staticmethod
    def build_gaussi_pyramid(high_res, layers):
        this_flash = [high_res]
        for i in range(1, layers):
            # 先对当前权重做高斯模糊, 然后下采样
            blurred = cv2.GaussianBlur(this_flash[i - 1], (5, 5), 0.83)
            blurred = blurred[::2, ::2]
            this_flash.append(blurred)
        return this_flash

    # 根据已知的高斯金字塔, 从最底层开始上采样, 得到每一个尺度的 laplace 细节
    @staticmethod
    def build_laplace_pyramaid(gaussi_pyramid, layers):
        upsampled = gaussi_pyramid[layers - 1]
        pyramid = [upsampled]
        for i in range(layers - 1, 0, -1):
            size = (gaussi_pyramid[i - 1].shape[1], gaussi_pyramid[i - 1].shape[0])
            # upsampled = cv2.resize(upsampled, size) # 假如我一直上采样呢
            upsampled = cv2.resize(gaussi_pyramid[i], size)
            pyramid.append(gaussi_pyramid[i - 1] - upsampled)
        pyramid.reverse()  # 目前分辨率都是从高到低排列的
        return pyramid