"""
This file is part of OpenCV project.
It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at http://opencv.org/license.html.

Copyright (C) 2025, Bigvision LLC.

MODNet Alpha Matting with OpenCV DNN

This sample demonstrates human portrait alpha matting using MODNet model.
MODNet is a trimap-free portrait matting method that can produce high-quality
alpha mattes for portrait images in real-time.

Reference:
    Github: https://github.com/ZHKKKe/MODNet

To download the MODNet model, run:
    python download_models.py modnet

Usage:
    python alpha_matting.py --input=image.jpg
"""

import cv2 as cv
import numpy as np
import argparse
import os
from common import *


def get_args_parser(func_args):
    backends = ("default", "openvino", "opencv", "vkcom", "cuda")
    targets = (
        "cpu",
        "opencl",
        "opencl_fp16",
        "ncs2_vpu",
        "hddl_vpu",
        "vulkan",
        "cuda",
        "cuda_fp16",
    )

    parser = argparse.ArgumentParser(add_help=False)
    parser.add_argument(
        "--zoo",
        default=os.path.join(os.path.dirname(os.path.abspath(__file__)), "models.yml"),
        help="An optional path to file with preprocessing parameters.",
    )
    parser.add_argument(
        "--input",
        default="messi5.jpg",
        help="Path to input image or video file. Defaults to messi5.jpg in samples/data.",
    )
    parser.add_argument(
        "--backend",
        default="default",
        type=str,
        choices=backends,
        help="Choose one of computation backends: "
        "default: automatically (by default), "
        "openvino: Intel's Deep Learning Inference Engine, "
        "opencv: OpenCV implementation, "
        "vkcom: VKCOM, "
        "cuda: CUDA",
    )
    parser.add_argument(
        "--target",
        default="cpu",
        type=str,
        choices=targets,
        help="Choose one of target computation devices: "
        "cpu: CPU target (by default), "
        "opencl: OpenCL, "
        "opencl_fp16: OpenCL fp16 (half-float precision), "
        "ncs2_vpu: NCS2 VPU, "
        "hddl_vpu: HDDL VPU, "
        "vulkan: Vulkan, "
        "cuda: CUDA, "
        "cuda_fp16: CUDA fp16 (half-float precision)",
    )

    args, _ = parser.parse_known_args()
    add_preproc_args(args.zoo, parser, "alpha_matting", "modnet")
    parser = argparse.ArgumentParser(
        parents=[parser],
        description="""
        To run:
            python alpha_matting.py --input=path/to/your/input/image

        Model path can also be specified using --model argument
        """,
        formatter_class=argparse.RawTextHelpFormatter,
    )
    return parser.parse_args(func_args)


def postprocess_output(image, alpha_output):
    """Process model output to create alpha mask."""
    h, w = image.shape[:2]

    alpha = alpha_output[0, 0] if alpha_output.ndim == 4 else alpha_output[0]
    alpha = cv.resize(alpha, (w, h))
    alpha = np.clip(alpha, 0, 1)

    alpha_mask = (alpha * 255).astype(np.uint8)

    return alpha_mask


def loadModel(args, engine):
    net = cv.dnn.readNetFromONNX(args.model, engine)
    net.setPreferableBackend(get_backend_id(args.backend))
    net.setPreferableTarget(get_target_id(args.target))
    return net


def draw_label(img, text, color):
    h, w = img.shape[:2]
    font_scale = max(h, w) / 1000.0
    thickness = 1
    text_size, _ = cv.getTextSize(text, cv.FONT_HERSHEY_SIMPLEX, font_scale, thickness)
    x = 10
    y = text_size[1] + 10
    cv.putText(img, text, (x, y), cv.FONT_HERSHEY_SIMPLEX, font_scale, color, thickness)


def apply_modnet(args, model, image):
    inp = cv.dnn.blobFromImage(
        image, args.scale, (args.width, args.height), args.mean, swapRB=args.rgb
    )
    model.setInput(inp)
    t0 = cv.getTickCount()
    out = model.forward()
    t = (cv.getTickCount() - t0) / cv.getTickFrequency()
    alpha_mask = postprocess_output(image, out)
    alpha_3ch = cv.merge([alpha_mask / 255.0, alpha_mask / 255.0, alpha_mask / 255.0])
    composite = (image.astype(np.float32) * alpha_3ch).astype(np.uint8)
    return alpha_mask, composite, t


def main(func_args=None):
    args = get_args_parser(func_args)
    engine = cv.dnn.ENGINE_AUTO
    if args.backend != "default" or args.target != "cpu":
        engine = cv.dnn.ENGINE_CLASSIC

    image = cv.imread(cv.samples.findFile(args.input))
    if image is None:
        print("Failed to load the input image")
        exit(-1)

    cv.namedWindow("Input", cv.WINDOW_AUTOSIZE)
    cv.namedWindow("Alpha Mask", cv.WINDOW_AUTOSIZE)
    cv.namedWindow("Composite", cv.WINDOW_AUTOSIZE)
    cv.moveWindow("Alpha Mask", 200, 50)
    cv.moveWindow("Composite", 400, 50)

    args.model = findModel(args.model, args.sha1)
    net = loadModel(args, engine)

    alpha_mask, composite, t = apply_modnet(args, net, image)
    label = "Inference time: %.2f ms" % (t * 1000.0)

    draw_label(image, label, (0, 255, 0))
    draw_label(alpha_mask, label, (255, 255, 255))
    draw_label(composite, label, (0, 255, 0))
    cv.imshow("Input", image)
    cv.imshow("Alpha Mask", alpha_mask)
    cv.imshow("Composite", composite)

    print("Press any key to exit")
    cv.waitKey(0)
    cv.destroyAllWindows()


if __name__ == "__main__":
    main()
