"""
cDNA微阵列图像处理 —— 简化版网格划分
======================================

算法步骤（适合课堂讲解）：

1. 彩色图 → 灰度图
2. 横轴投影：对每一列的所有像素灰度值求和 → 得到一条曲线
   纵轴投影：对每一行的所有像素灰度值求和 → 得到一条曲线
3. 在曲线上，求出 max 和 min，阈值 X = (max - min) × 10%
4. 曲线上每个值都减去 X
5. 减完之后：
   - 大于 0 的地方 = 斑点区域
   - 小于 0 的地方 = 斑点之间的空隙
   - 等于 0 的地方 = 斑点与空隙的分界线（过零点）
6. 配对相邻的过零点（离开斑点 + 进入下一个斑点），
   中点就是空隙的中心 = 划线位置
"""

import os
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
from skimage import color

plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False

# 路径设置
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
BASE_DIR = os.path.dirname(SCRIPT_DIR)
DATA_DIR = os.path.join(BASE_DIR, 'cDNA图像处理实例', '数据', 'cDNA')
OUTPUT_DIR = os.path.join(BASE_DIR, 'results_simple')


def draw_grid_lines(gray: np.ndarray, pct: float = 0.10):
    """
    核心算法：检测网格分割线。

    原理：
    灰度图的每一列/行，属于斑点的像素灰度值高，属于背景的灰度值低。
    把每列/行的灰度值加起来，就能得到一条曲线：
    ——曲线凸起的地方 = 斑点所在位置
    ——曲线凹陷的地方 = 斑点之间的空隙

    去掉一个阈值后，曲线在空隙处会变成负数，
    过零点的位置就是斑点和空隙的分界线，
    两个分界线中点就是划线位置。

    参数:
        gray: 灰度图 (高×宽)
        pct:  阈值百分比，默认10%

    返回:
        (纵线x坐标列表, 横线y坐标列表)
    """
    H, W = gray.shape

    # ================================================================
    # 步骤1：横轴投影 —— 统计每一列的灰度总和
    # ================================================================
    # gray 是一个 H×W 的二维数组，gray[行, 列] 是某个像素的灰度值
    # np.sum(gray, axis=0) 沿行方向求和 → 得到长度为 W 的一维数组
    # 含义：每一列上所有像素的灰度值加起来
    # 斑点所在列 → 亮像素多 → 和较大（曲线凸起）
    # 空隙所在列 → 暗像素多 → 和较小（曲线凹陷）
    col_profile = np.sum(gray, axis=0).astype(float)

    # ================================================================
    # 步骤2：纵轴投影 —— 统计每一行的灰度总和
    # ================================================================
    # np.sum(gray, axis=1) 沿列方向求和 → 得到长度为 H 的一维数组
    # 含义：每一行上所有像素的灰度值加起来
    row_profile = np.sum(gray, axis=1).astype(float)

    # ================================================================
    # 步骤3：计算阈值 X = (max - min) × 10%
    # ================================================================
    # max-min 是曲线的"振幅"，取10%作为阈值
    # 大于这个阈值的才是真正的斑点信号，小于的是噪声
    col_T = (np.max(col_profile) - np.min(col_profile)) * pct
    row_T = (np.max(row_profile) - np.min(row_profile)) * pct

    # ================================================================
    # 步骤4：曲线上所有值减去阈值
    # ================================================================
    # 减去阈值后：
    #   原本在空隙处的值（本来就小）→ 变成负数
    #   原本在斑点处的值（本来就大）→ 仍然为正数
    # 等于0的位置 = 斑点与空隙的分界线 = 过零点
    col_shifted = col_profile - col_T
    row_shifted = row_profile - row_T

    # ================================================================
    # 步骤5：找过零点，两两配对，中间点即划线位置
    # ================================================================
    def find_gap_lines(prof_shifted: np.ndarray) -> np.ndarray:
        """
        在减去阈值后的投影曲线上，找到每两个斑点之间的空隙中线。

        图解说明（设阈值为30）：
        原始曲线:   ___/‾‾‾\___/‾‾‾‾\___/‾‾‾\___
        数值:      10  50 60 55 8 45 70 48 5 55 50 12
        减阈值后:  -20 20 30 25 -22 15 40 18 -25 25 20 -18
        正负:      ─   +   +  +  ─   +  +  +  ─   +  +  ─
                          ↑         ↑         ↑          ↑
                       过零点    过零点    过零点    过零点

        过零点之间的区域：
        第一个 + 区域 = 一个斑点    (过零点1 → 过零点2)
        第一个 - 区域 = 空隙        (过零点2 → 过零点3)  ← 我们要的！
        第二个 + 区域 = 下一个斑点  (过零点3 → 过零点4)

        划线位置 = 空隙（负数区域）的中点
                 = (离开斑点的过零点 + 进入下一个斑点的过零点) / 2
        """
        # 判断每个位置是正（斑点）还是负（空隙）
        is_positive = prof_shifted > 0

        # 收集所有符号变化的位置（过零点）
        crossings = []
        for i in range(1, len(is_positive)):
            # 如果当前和前一个正负不同 → 发生了跨越零点
            if is_positive[i] != is_positive[i - 1]:
                crossings.append(i)

        if len(crossings) < 2:
            return np.array([])

        # 过零点交替：正→负，负→正，正→负，负→正……
        # 我们要的是「空隙区域」的中点 → 配对「离开斑点 → 进入下一个斑点」
        # 即：从第一个"正→负"开始配对

        # 如果开头就是负值（图像左侧是空隙），第一个过零点是"负→正"，
        # 跳过它，从下一个"正→负"开始
        start = 1 if not is_positive[0] else 0

        lines = []
        for k in range(start, len(crossings) - 1, 2):
            if k + 1 < len(crossings):
                # crossings[k]:   正→负（离开斑点）
                # crossings[k+1]: 负→正（进入下一个斑点）
                # 中点 = 空隙中央 = 划线位置
                mid = int((crossings[k] + crossings[k + 1]) / 2)
                lines.append(mid)

        return np.array(lines)

    x_lines = find_gap_lines(col_shifted)
    y_lines = find_gap_lines(row_shifted)

    return x_lines, y_lines


def main():
    os.makedirs(OUTPUT_DIR, exist_ok=True)

    # ---- 读取图像并转为灰度图 ----
    img = np.array(Image.open(os.path.join(DATA_DIR, 'cDNA.png')))
    # 原图是 RGBA（红绿蓝+透明通道），取前三个通道转为灰度
    gray = (color.rgb2gray(img[:, :, :3]) * 255).astype(np.uint8)

    # ---- 执行网格检测 ----
    x_lines, y_lines = draw_grid_lines(gray)
    print(f"检测到 {len(x_lines)} 条纵线, {len(y_lines)} 条横线")

    # ---- 在原图上划线并保存 ----
    fig, ax = plt.subplots(figsize=(8, 8))
    ax.imshow(gray, cmap='gray')

    # 画纵向分割线（竖线）
    for x in x_lines:
        ax.axvline(x=x, color='lime', linewidth=0.5)

    # 画横向分割线（横线）
    for y in y_lines:
        ax.axhline(y=y, color='lime', linewidth=0.5)

    ax.set_title(f'cDNA微阵列网格划分 ({len(x_lines)}×{len(y_lines)})', fontsize=14)
    ax.axis('off')

    out_path = os.path.join(OUTPUT_DIR, 'gridding_simple.png')
    fig.savefig(out_path, dpi=150, bbox_inches='tight')
    plt.close(fig)
    print(f"保存: {out_path}")


if __name__ == '__main__':
    main()
