import os
import json
import shutil
import numpy as np
import cv2
from scipy.optimize import leastsq
from tqdm import tqdm


def calc_R(x, y, xc, yc):
    return np.sqrt((x - xc) ** 2 + (y - yc) ** 2)


def f(c, x, y):
    Ri = calc_R(x, y, *c)
    return Ri - Ri.mean()

def fit_arc_and_create_mask(points, shape, point_step_deg=0.5, radius=2):
    x_data = points[:, 0]
    y_data = points[:, 1]
    center_estimate = np.mean(x_data), np.mean(y_data)
    try:
        center, _ = leastsq(f, center_estimate, args=(x_data, y_data))
        xc, yc = center
        Ri = calc_R(x_data, y_data, xc, yc)
        R = Ri.mean()

        h, w = shape
        if R <= 0 or not np.isfinite(R) or not np.isfinite(xc) or not np.isfinite(yc):
            raise ValueError("拟合圆非法")

        # 生成完整圆的点
        circle_angles = np.linspace(0, 2 * np.pi, int(360 / point_step_deg), endpoint=False)
        full_circle_points = np.stack([
            xc + R * np.cos(circle_angles),
            yc + R * np.sin(circle_angles)
        ], axis=-1).astype(np.float32)

        # 只保留图像内部的点
        in_bounds_mask = (
            (full_circle_points[:, 0] >= 0) & (full_circle_points[:, 0] < w) &
            (full_circle_points[:, 1] >= 0) & (full_circle_points[:, 1] < h)
        )
        clipped_points = full_circle_points[in_bounds_mask]

        if len(clipped_points) < 3:
            print(f"拟合失败使用的三个点为：\n{points}")
            raise ValueError("拟合圆点全部在图像外")

        return None, clipped_points.tolist()

    except Exception as e:
        print(f"⚠️ 圆拟合失败：{e}")
        return None, []





def build_shapes_from_points(points, label="arc"):
    points_np = np.array(points)
    if points_np.shape[0] < 3:
        return []

    xs = points_np[:, 0]
    ys = points_np[:, 1]

    shape = {
        "label": label,
        "points": points,
        "shape_type": "polygon",
        "flags": {},
        "xmin": int(xs.min()),
        "ymin": int(ys.min()),
        "xmax": int(xs.max()),
        "ymax": int(ys.max())
    }
    return [shape]


def process_folder_labelme(input_dir, output_dir, point_step_deg=0.5, radius=2):
    os.makedirs(output_dir, exist_ok=True)

    for file_name in tqdm(os.listdir(input_dir)):
        if not file_name.endswith(".json"):
            continue

        json_path = os.path.join(input_dir, file_name)
        image_path = json_path.replace(".json", ".jpg")
        if not os.path.exists(image_path):
            print(f"图像不存在，跳过：{image_path}")
            continue

        with open(json_path, "r") as f:
            label_data = json.load(f)

        arc_points_raw = [item['points'][0] for item in label_data['shapes']
                          if item.get('label') == 'arc' and len(item.get('points', [])) == 1]

        if len(arc_points_raw) < 3:
            print(f"{file_name} 中 arc 点数不足 3，跳过")
            continue

        image = cv2.imread(image_path)
        h, w = image.shape[:2]

        arc_shapes = []
        num_groups = len(arc_points_raw) // 3

        for i in range(num_groups):
            group_points = arc_points_raw[i*3:(i+1)*3]
            if len(group_points) < 3:
                print(f"{file_name} 第 {i+1} 组点数不足 3，跳过")
                continue
            points = np.array(group_points)
            try:
                _, arc_pts = fit_arc_and_create_mask(points, (h, w), point_step_deg, radius)
                shapes = build_shapes_from_points(arc_pts, label="arc")
                arc_shapes.extend(shapes)
            except Exception as e:
                print(f"{file_name} 第 {i+1} 组拟合失败，跳过。错误：{e}")
                continue

        if len(arc_shapes) == 0:
            print(f"{file_name} 没有成功拟合的 arc 区域，跳过")
            continue

        output_json = {
            "version": "5.0.1",
            "flags": {},
            "shapes": arc_shapes,
            "imagePath": os.path.basename(image_path),
            "imageHeight": h,
            "imageWidth": w
        }

        base_name = os.path.splitext(os.path.basename(image_path))[0]
        out_json_path = os.path.join(output_dir, base_name + ".json")
        out_img_path = os.path.join(output_dir, base_name + ".jpg")

        with open(out_json_path, "w") as f:
            json.dump(output_json, f, indent=4)

        shutil.copy(image_path, out_img_path)

    print(f"\n✅ 所有 arc 区域已保存为 labelme 格式（图像 + json）到目录：{output_dir}")


# ===== 修改为你自己的输入输出路径 =====
input_dir = r"G:\python_ws_g\data\test\jsonjpg_out"
output_dir = r"G:\python_ws_g\data\test\jsonout"
point_step_deg = 0.5
draw_radius = 2

if __name__ == "__main__":
    process_folder_labelme(input_dir, output_dir, point_step_deg, draw_radius)
