# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license

from __future__ import annotations

import inspect
from collections.abc import Iterator
from pathlib import Path
from typing import Any

import numpy as np
import torch
from PIL import Image

from ultralytics.cfg import QUANTIZE_ALIASES, TASK2DATA, _handle_deprecation, get_cfg, get_save_dir
from ultralytics.engine.results import Results
from ultralytics.nn.tasks import BaseModel, guess_model_task, load_checkpoint, yaml_model_load
from ultralytics.utils import (
    ARGV,
    ASSETS,
    DEFAULT_CFG_DICT,
    LOGGER,
    PLATFORM_URL,
    RANK,
    SETTINGS,
    YAML,
    callbacks,
    checks,
)
from ultralytics.utils.torch_utils import unwrap_model

# Predictor arguments applied at model or tracker setup, so changing one rebuilds the predictor and its trackers
PREDICTOR_SETUP_KEYS = ("device", "dnn", "data", "nms", "compile", "channels_last", "quantize", "tracker")


class Model(torch.nn.Module):
    """A base class for implementing YOLO models, unifying APIs across different model types.

    This class provides a common interface for various operations related to YOLO models, such as training, validation,
    prediction, exporting, and benchmarking. It handles different types of models, including those loaded from local
    files or Triton Server.

    Attributes:
        callbacks (dict): A dictionary of callback functions for various events during model operations.
        predictor (BasePredictor): The predictor object used for making predictions.
        model (torch.nn.Module): The underlying PyTorch model.
        trainer (BaseTrainer): The trainer object used for training the model.
        ckpt (dict): The checkpoint data of the loaded weights, if any.
        cfg (str): The configuration of the model if loaded from a *.yaml file.
        ckpt_path (str): The path to the checkpoint file.
        overrides (dict): A dictionary of overrides for model configuration.
        metrics (ultralytics.utils.metrics.DetMetrics): The latest training/validation metrics.
        task (str): The type of task the model is intended for.
        model_name (str): The name of the model.

    Methods:
        __call__: Alias for the predict method, enabling the model instance to be callable.
        _new: Initialize a new model based on a configuration file.
        _load: Load a model from a checkpoint file.
        _check_is_pytorch_model: Ensure that the model is a PyTorch model.
        reset_weights: Reset the model's weights to their initial state.
        load: Load model weights from a specified file.
        save: Save the current state of the model to a file.
        info: Log or return information about the model.
        fuse: Fuse Conv2d and BatchNorm2d layers for optimized inference.
        embed: Generate image embeddings from intermediate model layers.
        predict: Perform predictions on given image sources.
        track: Perform object tracking.
        val: Validate the model on a dataset.
        calibrate: Fit scale-only depth calibration for depth models.
        benchmark: Benchmark the model on various export formats.
        export: Export the model to different formats.
        train: Train the model on a dataset.
        tune: Perform hyperparameter tuning.
        _apply: Apply a function to the model's tensors.
        add_callback: Add a callback function for an event.
        clear_callback: Clear all callbacks for an event.
        reset_callbacks: Reset all callbacks to their default functions.

    Examples:
        >>> from ultralytics import YOLO
        >>> model = YOLO("yolo26n.pt")
        >>> results = model.predict("image.jpg")
        >>> model.train(data="coco8.yaml", epochs=3)
        >>> metrics = model.val()
        >>> model.export(format="onnx")
    """

    def __init__(
        self,
        model: str | Path | Model = "yolo26n.pt",
        task: str | None = None,
        verbose: bool = False,
    ) -> None:
        """Initialize a new instance of the YOLO model class.

        This constructor sets up the model based on the provided model path or name. It handles various types of model
        sources, including local files and Triton Server models. The method initializes several important attributes of
        the model and prepares it for operations like training, prediction, or export.

        Args:
            model (str | Path | Model): Path or name of the model to load or create. Can be a local file path, a Triton
                Server model, or an already initialized Model instance.
            task (str, optional): The specific task for the model. If None, it will be inferred from the config.
            verbose (bool): If True, enables verbose output during the model's initialization and subsequent operations.

        Raises:
            FileNotFoundError: If the specified model file does not exist or is inaccessible.
            ValueError: If the model file or configuration is invalid or unsupported.
        """
        if isinstance(model, Model):
            self.__dict__ = model.__dict__  # accepts an already initialized Model
            return
        super().__init__()
        self.callbacks = callbacks.get_default_callbacks()
        self.predictor = None  # reuse predictor
        self.model = None  # model object
        self.trainer = None  # trainer object
        self.ckpt = {}  # if loaded from *.pt
        self.cfg = None  # if loaded from *.yaml
        self.ckpt_path = None
        self.overrides = {}  # overrides for trainer object
        self.metrics = None  # validation/training metrics
        self.task = task  # task type
        self.model_name = None  # model name
        model = str(model).strip()

        # Check if Triton Server model
        if self.is_triton_model(model):
            self.model_name = self.model = model
            self.overrides["task"] = task or "detect"  # set `task=detect` if not explicitly set
            return

        # Load or create new YOLO model
        __import__("os").environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8"  # to avoid deterministic warnings
        if str(model).endswith((".yaml", ".yml")):
            self._new(model, task=task, verbose=verbose)
        else:
            self._load(model, task=task)

        # Delete super().training for accessing self.model.training
        del self.training

    def __call__(
        self,
        source: str | Path | int | Image.Image | list | tuple | np.ndarray | torch.Tensor | None = None,
        stream: bool = False,
        **kwargs: Any,
    ) -> Iterator[Results | torch.Tensor] | list[Results] | list[torch.Tensor]:
        """Alias for the predict method, enabling the model instance to be callable for predictions.

        This method simplifies the process of making predictions by allowing the model instance to be called directly
        with the required arguments.

        Args:
            source (str | Path | int | PIL.Image | np.ndarray | torch.Tensor | list | tuple): The source of the image(s)
                to make predictions on. Can be a file path, URL, PIL image, numpy array, PyTorch tensor, or a list/tuple
                of these.
            stream (bool): If True, treat the input source as a continuous stream for predictions.
            **kwargs (Any): Additional keyword arguments to configure the prediction process.

        Returns:
            (Iterator[ultralytics.engine.results.Results | torch.Tensor] | list[ultralytics.engine.results.Results] |
            list[torch.Tensor]): Prediction results or embeddings, streamed when `stream=True`.

        Examples:
            >>> model = YOLO("yolo26n.pt")
            >>> results = model("https://ultralytics.com/images/bus.jpg")
            >>> for r in results:
            ...     print(f"Detected {len(r)} objects in image")
        """
        return self.predict(source, stream, **kwargs)

    @staticmethod
    def is_triton_model(model: str) -> bool:
        """Check if the given model string is a Triton Server URL.

        This static method determines whether the provided model string represents a valid Triton Server URL by parsing
        its components using urllib.parse.urlsplit().

        Args:
            model (str): The model string to be checked.

        Returns:
            (bool): True if the model string is a valid Triton Server URL, False otherwise.

        Examples:
            >>> Model.is_triton_model("http://localhost:8000/v2/models/yolo26n")
            True
            >>> Model.is_triton_model("yolo26n.pt")
            False
        """
        from urllib.parse import urlsplit

        url = urlsplit(model)
        return bool(url.netloc and url.path) and url.scheme in {"http", "grpc"}

    def _new(self, cfg: str, task: str | None = None, verbose: bool = False) -> None:
        """Initialize a new model and infer the task type from model definitions.

        Creates a new model instance based on the provided configuration file. Loads the model configuration, infers the
        task type if not specified, and initializes the model using the appropriate class from the task map.

        Args:
            cfg (str): Path to the model configuration file in YAML format.
            task (str, optional): The specific task for the model. If None, it will be inferred from the config.
            verbose (bool): If True, displays model information during loading.

        Raises:
            ValueError: If the configuration file is invalid or the task cannot be inferred.
            ImportError: If the required dependencies for the specified task are not installed.

        Examples:
            >>> model = YOLO("yolo26n.pt")
            >>> model._new("yolo26n.yaml", task="detect", verbose=True)
        """
        cfg_dict = yaml_model_load(cfg)
        self.cfg = cfg
        self.task = task or guess_model_task(cfg_dict)
        self.model = self._smart_load("model")(cfg_dict, verbose=verbose and RANK == -1)  # build model
        self.overrides["model"] = self.cfg
        self.overrides["task"] = self.task

        # Below added to allow export from YAMLs
        self.model.args = {**DEFAULT_CFG_DICT, **self.overrides}  # combine default and model args (prefer model args)
        self.model.task = self.task
        self.model_name = cfg

    def _load(self, weights: str, task: str | None = None) -> None:
        """Load a model from a checkpoint file or initialize it from a weights file.

        This method handles loading models from either .pt checkpoint files or other weight file formats. It sets up the
        model, task, and related attributes based on the loaded weights.

        Args:
            weights (str): Path to the model weights file to be loaded.
            task (str, optional): The task associated with the model. If None, it will be inferred from the model.

        Raises:
            FileNotFoundError: If the specified weights file does not exist or is inaccessible.
            ValueError: If the weights file format is unsupported or invalid.

        Examples:
            >>> model = Model()
            >>> model._load("yolo26n.pt")
            >>> model._load("path/to/weights.pth", task="detect")
        """
        model_uri = checks.normalize_platform_uri(weights)
        if weights.lower().startswith(checks.REMOTE_FILE_PREFIXES):
            weights = checks.check_file(weights, download_dir=SETTINGS["weights_dir"])  # download and return local file
        weights = checks.check_model_file_from_stem(weights)  # add suffix, i.e. yolo26n -> yolo26n.pt

        if str(weights).rpartition(".")[-1] == "pt":
            self.model, self.ckpt = load_checkpoint(weights)
            self.task = self.model.task
            self.overrides = self.model.args = self._reset_ckpt_args(self.model.args)
            self.ckpt_path = self.model.pt_path
        else:
            weights = checks.check_file(weights)  # runs in all cases, not redundant with above call
            self.model, self.ckpt = weights, None
            self.task = task or guess_model_task(weights)
            self.ckpt_path = weights
        self.overrides["model"] = model_uri if str(model_uri).startswith("ul://") else weights
        self.overrides["task"] = self.task
        self.model_name = weights

    def _check_is_pytorch_model(self) -> None:
        """Check if the model is a PyTorch model and raise TypeError if it's not.

        This method verifies that the model is either a PyTorch module or a .pt file. It's used to ensure that certain
        operations that require a PyTorch model are only performed on compatible model types.

        Raises:
            TypeError: If the model is not a PyTorch module or a .pt file. The error message provides detailed
                information about supported model formats and operations.

        Examples:
            >>> model = Model("yolo26n.pt")
            >>> model._check_is_pytorch_model()  # No error raised
            >>> model = Model("yolo26n.onnx")
            >>> model._check_is_pytorch_model()  # Raises TypeError
        """
        pt_str = isinstance(self.model, (str, Path)) and str(self.model).rpartition(".")[-1] == "pt"
        pt_module = isinstance(self.model, torch.nn.Module)
        if not (pt_module or pt_str):
            raise TypeError(
                f"model='{self.model}' should be a *.pt PyTorch model to run this method, but is a different format. "
                f"PyTorch models can train, val, predict and export, i.e. 'model.train(data=...)', but exported "
                f"formats like ONNX, TensorRT etc. only support 'predict' and 'val' modes, "
                f"i.e. 'yolo predict model=yolo26n.onnx'.\nTo run CUDA or MPS inference please pass the device "
                f"argument directly in your inference command, i.e. 'model.predict(source=..., device=0)'"
            )

    def reset_weights(self) -> Model:
        """Reset the model's weights to their initial state.

        This method iterates through all modules in the model and resets their parameters if they have a
        'reset_parameters' method. It also ensures that all parameters have 'requires_grad' set to True, enabling them
        to be updated during training.

        Returns:
            (Model): The instance of the class with reset weights.

        Raises:
            TypeError: If the model is not a PyTorch model.

        Examples:
            >>> model = Model("yolo26n.pt")
            >>> model.reset_weights()
        """
        self._check_is_pytorch_model()
        for m in self.model.modules():
            if hasattr(m, "reset_parameters"):
                m.reset_parameters()
        for p in self.model.parameters():
            p.requires_grad = True
        self.overrides["pretrained"] = False  # train() builds the seeded model from the architecture
        self.predictor = None
        return self

    def load(self, weights: str | Path | dict | torch.nn.Module = "yolo26n.pt") -> Model:
        """Load parameters from the specified weights file into the model.

        This method supports loading weights from a file or directly from a weights object. It matches parameters by
        name and shape and transfers them to the model.

        Args:
            weights (str | Path | dict | torch.nn.Module): Path to the weights file, a checkpoint dict or a module.

        Returns:
            (Model): The instance of the class with loaded weights.

        Raises:
            TypeError: If the model is not a PyTorch model.

        Examples:
            >>> model = Model()
            >>> model.load("yolo26n.pt")
            >>> model.load(Path("path/to/weights.pt"))
        """
        self._check_is_pytorch_model()
        if isinstance(weights, (str, Path)):
            weights, ckpt = load_checkpoint(weights)
            ckpt_path = weights.pt_path
        else:
            ckpt, ckpt_path = {"model": self.model}, None  # an object load has no file to resume from
        self.overrides.pop("pretrained", None)
        self.model.load(weights)
        self.ckpt, self.ckpt_path = ckpt, ckpt_path  # train() seeds from self.model while ckpt is set
        self.predictor = None
        return self

    def save(self, filename: str | Path = "saved_model.pt") -> None:
        """Save the current model state to a file.

        This method exports the model's checkpoint (ckpt) to the specified filename. It includes metadata such as the
        date, Ultralytics version, license information, and a link to the documentation. The module is written as held
        in memory: layers folded by ``fuse()`` stay folded, so training from the file transfers less.

        Args:
            filename (str | Path): The name of the file to save the model to.

        Raises:
            TypeError: If the model is not a PyTorch model.

        Examples:
            >>> model = Model("yolo26n.pt")
            >>> model.save("my_model.pt")
        """
        self._check_is_pytorch_model()
        from copy import deepcopy
        from datetime import datetime

        from ultralytics import __version__
        from ultralytics.utils.torch_utils import qat_state, strip_qat

        state = qat_state(self.model) if isinstance(self.model, torch.nn.Module) else None
        model = (
            deepcopy(self.model).half().to(memory_format=torch.contiguous_format)
            if isinstance(self.model, torch.nn.Module)
            else self.model
        )
        if isinstance(model, torch.nn.Module):
            strip_qat(model)
        updates = {
            "ema": None,
            "model": model,
            "modelopt": state,
            "date": datetime.now().astimezone().isoformat(),
            "version": __version__,
            "license": "AGPL-3.0 License (https://ultralytics.com/license)",
            "docs": "https://docs.ultralytics.com",
        }
        torch.save({**self.ckpt, **updates}, filename)

    def info(self, detailed: bool = False, verbose: bool = True, imgsz: int | list[int] = 640):
        """Display model information.

        This method provides an overview or detailed information about the model, depending on the arguments
        passed. It can control the verbosity of the output.

        Args:
            detailed (bool): If True, shows detailed information about the model layers and parameters.
            verbose (bool): If True, prints the information and returns model summary. If False, returns None.
            imgsz (int | list[int]): Input image size used for FLOPs calculation.

        Returns:
            (tuple): A tuple containing the number of layers (int), number of parameters (int), number of gradients
                (int), and GFLOPs (float). Returns None if verbose is False.

        Examples:
            >>> model = Model("yolo26n.pt")
            >>> model.info()  # Prints model summary and returns tuple
            >>> model.info(detailed=True)  # Prints detailed info and returns tuple
        """
        self._check_is_pytorch_model()
        return self.model.info(detailed=detailed, verbose=verbose, imgsz=imgsz)

    def fuse(self, verbose: bool = True, imgsz: int | list[int] = 640) -> Model:
        """Fuse Conv2d and BatchNorm2d layers in the model for optimized inference.

        This method iterates through the model's modules and fuses consecutive Conv2d and BatchNorm2d layers into a
        single layer. This fusion can significantly improve inference speed by reducing the number of operations and
        memory accesses required during forward passes.

        The fusion process typically involves folding the BatchNorm2d parameters (mean, variance, weight, and
        bias) into the preceding Conv2d layer's weights and biases. This results in a single Conv2d layer that
        performs both convolution and normalization in one step.

        Args:
            verbose (bool): Whether to print model information after fusion.
            imgsz (int | list[int]): Input image size used for FLOPs calculation.

        Returns:
            (Model): The model instance with fused layers.

        Raises:
            TypeError: If the model is not a PyTorch model.

        Examples:
            >>> model = Model("yolo26n.pt")
            >>> model.fuse()
            >>> # Model is now fused and ready for optimized inference
        """
        self._check_is_pytorch_model()
        # DistillationModel fuses to its student, so adopt the return
        self.model = self.model.fuse(verbose=verbose, imgsz=imgsz)
        self.predictor = None
        return self

    def embed(
        self,
        source: str | Path | int | list | tuple | np.ndarray | torch.Tensor | None = None,
        stream: bool = False,
        **kwargs: Any,
    ) -> Iterator[torch.Tensor] | list[torch.Tensor]:
        """Generate image embeddings based on the provided source.

        This method is a wrapper around the 'predict()' method, returning feature embeddings from image sources. By
        default, embeddings are extracted from the second-to-last model layer. Pass `embed=[layer_index]` in `kwargs` to
        select specific layers.

        Args:
            source (str | Path | int | list | tuple | np.ndarray | torch.Tensor): The source of the image for generating
                embeddings. Can be a file path, URL, numpy array, etc.
            stream (bool): If True, predictions are streamed.
            **kwargs (Any): Additional keyword arguments for configuring the embedding process.

        Returns:
            (Iterator[torch.Tensor] | list[torch.Tensor]): Image embeddings, streamed when `stream=True`.

        Raises:
            TypeError: If the model is not an Ultralytics PyTorch model. Exported formats and third-party modules expose
                no intermediate layers to embed.

        Examples:
            >>> model = YOLO("yolo26n.pt")
            >>> image = "https://ultralytics.com/images/bus.jpg"
            >>> embeddings = model.embed(image)
            >>> results = model.predict(image)
            >>> print(embeddings[0].shape)
            >>> print(results[0].boxes.shape)
        """
        self._check_is_pytorch_model()
        model = unwrap_model(self.model)
        if not isinstance(model, BaseModel):  # e.g. a super-gradients YOLO-NAS module, which has no layer list
            raise TypeError(f"model='{type(model).__name__}' is not an Ultralytics model and cannot be embedded.")
        if not kwargs.get("embed"):
            kwargs["embed"] = [len(model.model) - 2]  # embed second-to-last layer if no indices passed
        return self.predict(source, stream, **kwargs)

    def predict(
        self,
        source: str | Path | int | Image.Image | list | tuple | np.ndarray | torch.Tensor | None = None,
        stream: bool = False,
        predictor=None,
        **kwargs: Any,
    ) -> Iterator[Results | torch.Tensor] | list[Results] | list[torch.Tensor]:
        """Perform predictions on the given image source using the YOLO model.

        This method facilitates the prediction process, allowing various configurations through keyword arguments. It
        supports predictions with custom predictors or the default predictor method. The method handles different types
        of image sources and can operate in a streaming mode.

        Args:
            source (str | Path | int | PIL.Image | np.ndarray | torch.Tensor | list | tuple): The source of the image(s)
                to make predictions on. Accepts various types including file paths, URLs, PIL images, numpy arrays, and
                torch tensors.
            stream (bool): If True, treats the input source as a continuous stream for predictions.
            predictor (type[BasePredictor], optional): A custom predictor class for making predictions. If None, the
                method uses the default predictor for the task.
            **kwargs (Any): Additional keyword arguments for configuring the prediction process. These include `embed`
                for returning feature embeddings from specified layers.

        Returns:
            (Iterator[ultralytics.engine.results.Results | torch.Tensor] | list[ultralytics.engine.results.Results] |
            list[torch.Tensor]): Prediction results or embeddings, streamed when `stream=True`.

        Examples:
            >>> model = YOLO("yolo26n.pt")
            >>> results = model.predict(source="path/to/image.jpg", conf=0.25)
            >>> for r in results:
            ...     print(r.boxes.data)  # print detection bounding boxes

        Notes:
            - If 'source' is not provided, it defaults to the ASSETS directory (or a sample image for OBB) with a
              warning.
            - The method sets up a new predictor if not already present and updates its arguments with each call,
              rebuilding it when an argument applied at model or tracker setup changes.
            - For SAM-type models, 'prompts' can be passed as a keyword argument.
        """
        if source is None:
            source = "https://ultralytics.com/images/boats.jpg" if self.task == "obb" else ASSETS
            LOGGER.warning(f"'source' is missing. Using 'source={source}'.")

        is_cli = (ARGV[0].endswith("yolo") or ARGV[0].endswith("ultralytics")) and any(
            x in ARGV for x in ("predict", "track", "mode=predict", "mode=track")
        )

        custom = {"conf": 0.25, "batch": 1, "save": is_cli, "mode": "predict", "rect": True, "embed": None}
        kwargs = _handle_deprecation(kwargs)
        prompts = kwargs.pop("prompts", None)  # for SAM-type models
        args = {**self.overrides, **custom, **kwargs}  # highest priority args on the right
        args["quantize"] = QUANTIZE_ALIASES.get(str(q := args.get("quantize")).lower(), q)  # unset quantize is FP32

        if not self.predictor or any(
            getattr(self.predictor.args, k) != args[k] for k in PREDICTOR_SETUP_KEYS if k in args
        ):
            self.predictor = (predictor or self._smart_load("predictor"))(overrides=args, _callbacks=self.callbacks)
            self.predictor.setup_model(model=self.model, verbose=is_cli)
        else:  # only update args if predictor is already setup
            save_keys = ("project", "name", "save_dir", "exist_ok")
            prev_save_args = tuple(getattr(self.predictor.args, k, None) for k in save_keys)
            base_args = {
                **DEFAULT_CFG_DICT,
                **self.overrides,
                **{k: getattr(self.predictor.args, k) for k in PREDICTOR_SETUP_KEYS},
            }
            self.predictor.args = get_cfg(base_args, {**custom, **kwargs})
            if self.predictor.args.show:
                self.predictor.args.show = checks.check_imshow(warn=True)
            if prev_save_args != tuple(getattr(self.predictor.args, k, None) for k in save_keys):
                self.predictor.save_dir = get_save_dir(self.predictor.args)
        if prompts and hasattr(self.predictor, "set_prompts"):  # for SAM-type models
            self.predictor.set_prompts(prompts)
        return self.predictor.predict_cli(source=source) if is_cli else self.predictor(source=source, stream=stream)

    def track(
        self,
        source: str | Path | int | list | tuple | np.ndarray | torch.Tensor | None = None,
        stream: bool = False,
        persist: bool = False,
        **kwargs: Any,
    ) -> list[Results] | Iterator[Results]:
        """Conduct object tracking on the specified input source using the registered trackers.

        This method performs object tracking using the model's predictors and optionally registered trackers. It handles
        various input sources such as file paths or video streams, and supports customization through keyword arguments.
        The method registers or refreshes the tracking callbacks on each call, so a later `persist` takes effect.

        Args:
            source (str | Path | int | list | tuple | np.ndarray | torch.Tensor, optional): Input source for object
                tracking. Can be a file path, URL, or video stream.
            stream (bool): If True, treats the input source as a continuous video stream.
            persist (bool): If True, persists trackers between different calls to this method.
            **kwargs (Any): Additional keyword arguments for configuring the tracking process.

        Returns:
            (list[ultralytics.engine.results.Results] | Iterator[ultralytics.engine.results.Results]): Tracking results,
                streamed when `stream=True`.

        Examples:
            >>> model = YOLO("yolo26n.pt")
            >>> results = model.track(source="path/to/video.mp4", show=True)
            >>> for r in results:
            ...     print(r.boxes.id)  # print tracking IDs

        Notes:
            - This method sets a default confidence threshold of 0.1 so trackers receive low-confidence detections.
            - The tracking mode is explicitly set in the keyword arguments.
            - Batch size defaults to 1 for tracking in videos.
        """
        from ultralytics.trackers import register_tracker

        register_tracker(self, persist)
        kwargs["conf"] = 0.1 if kwargs.get("conf") is None else kwargs["conf"]  # trackers need low-confidence input
        kwargs["batch"] = kwargs.get("batch") or 1  # batch-size 1 for tracking in videos
        kwargs["mode"] = "track"
        return self.predict(source=source, stream=stream, **kwargs)

    def val(
        self,
        validator=None,
        **kwargs: Any,
    ):
        """Validate the model using a specified dataset and validation configuration.

        This method facilitates the model validation process, allowing for customization through various settings. It
        supports validation with a custom validator or the default validation approach. The method combines default
        configurations, method-specific defaults, and user-provided arguments to configure the validation process.

        Args:
            validator (type[ultralytics.engine.validator.BaseValidator], optional): A custom validator class for
                validating the model. If None, uses the default validator for the task.
            **kwargs (Any): Arbitrary keyword arguments for customizing the validation process.

        Returns:
            (ultralytics.utils.metrics.DetMetrics): Validation metrics obtained from the validation process. The
                specific metrics type depends on the task (e.g., DetMetrics, SegmentMetrics,
                ClassifyMetrics, PoseMetrics).

        Examples:
            >>> model = YOLO("yolo26n.pt")
            >>> results = model.val(data="coco8.yaml", imgsz=640)
            >>> print(results.box.map)  # Print mAP50-95
        """
        custom = {"rect": True}  # method defaults
        if kwargs.get("data") is None:
            kwargs["data"] = self._val_data()
        args = {**self.overrides, **custom, **kwargs, "mode": "val"}  # highest priority args on the right

        validator = (validator or self._smart_load("validator"))(args=args, _callbacks=self.callbacks)
        validator(model=self.model)
        self.metrics = validator.metrics
        return validator.metrics

    def calibrate(self, data: str | Path, **kwargs: Any):
        """Fit scale-only depth calibration on a small labeled set (depth task only).

        Runs a validation pass, then fits the global log-affine ``d' = exp(a·log d + b)`` against
        ground-truth depth via :func:`fit_calibration_selective` — the same "calibrate only if it
        helps" policy as trainer auto-calibration (identity / scale-only chosen by held-out δ1) —
        which writes ``(a, b)`` into the head's ``cal_a``/``cal_b`` buffers. No gradient training
        and the decoder weights are untouched, so it cannot degrade the relative depth structure.
        Call ``model.save(...)`` afterwards to persist the calibration.

        Args:
            data (str | Path): Dataset YAML providing a labeled split to calibrate against.
            **kwargs (Any): Extra validation arguments (e.g. ``imgsz``, ``batch``, ``device``, ``split``).

        Returns:
            (tuple | None): The fitted ``(a, b)``, or ``None`` if fewer than 2 images had valid depth pixels.

        Raises:
            TypeError: If the model is not a PyTorch model.
            ValueError: If the model is not a depth model or has no Depth head with calibration buffers.

        Examples:
            >>> model = YOLO("yolo26s-depth.pt")
            >>> model.calibrate(data="my_depth_dataset.yaml")
            >>> model.save("yolo26s-depth-calibrated.pt")
        """
        self._check_is_pytorch_model()
        if self.task != "depth":
            raise ValueError(f"calibrate() is only supported for depth models (task='depth'), got task={self.task!r}.")
        from ultralytics.models.yolo.depth.calibrate import _depth_head, fit_calibration_selective

        if _depth_head(self.model) is None:
            raise ValueError("Model has no Depth head with calibration buffers (cal_a/cal_b).")
        args = {**self.overrides, **kwargs, "data": data, "mode": "val", "task": "depth"}
        validator = self._smart_load("validator")(args=args, _callbacks=self.callbacks)
        validator(model=self.model)
        self.predictor = None  # calibration updates the retained model below
        res = fit_calibration_selective(
            self.model, validator.dataloader, validator.device, max_depth=validator.data.get("max_depth") or 100.0
        )
        if res is None:
            return None
        LOGGER.info("Call model.save(...) to persist the calibration.")
        return res["a"], res["b"]

    def benchmark(self, data: str | None = None, format: str = "", verbose: bool | float = False, **kwargs: Any):
        """Benchmark the model across various export formats to evaluate performance.

        This method assesses the model's performance in different export formats, such as ONNX, TorchScript, etc. It
        uses the 'benchmark' function from the ultralytics.utils.benchmarks module. The benchmarking is configured using
        a combination of default configuration values, model-specific arguments, method-specific defaults, and any
        additional user-provided keyword arguments.

        Args:
            data (str | None): Path to the dataset for benchmarking. If None, uses default dataset for the task.
            format (str): Export format name for specific benchmarking.
            verbose (bool | float): If True or a float, raise on non-assertion benchmark failures; a float also asserts
                that every successful format's metric exceeds this floor value.
            **kwargs (Any): Arbitrary keyword arguments to customize the benchmarking process. Common options include:
                - imgsz (int | list[int]): Image size for benchmarking.
                - quantize (int | str): Requested precision: 16 (FP16), 8 (INT8), or 32/None (FP32) where
                  supported; only 16 changes the native PyTorch row.
                - device (str): Device to run the benchmark on (e.g., 'cpu', 'cuda').

        Returns:
            (polars.DataFrame): A Polars DataFrame with benchmark results for each format, including file size, metric,
                and inference time.

        Raises:
            TypeError: If the model is not a PyTorch model.

        Examples:
            >>> model = YOLO("yolo26n.pt")
            >>> results = model.benchmark(data="coco8.yaml", imgsz=640, quantize=16)
            >>> print(results)
        """
        self._check_is_pytorch_model()
        from ultralytics.utils.benchmarks import benchmark

        from .exporter import export_formats

        custom = {"verbose": False, "nms": None}  # method defaults
        kwargs = _handle_deprecation(kwargs)  # forward legacy flags (e.g. half/int8 -> quantize) before merging
        args = {**DEFAULT_CFG_DICT, **self.overrides, **custom, **kwargs, "mode": "benchmark"}
        fmts = export_formats()
        export_args = set(dict(zip(fmts["Argument"], fmts["Arguments"])).get(format.lower(), [])) - {
            "batch",
            "data",
            "quantize",
            "nms",
        }
        export_kwargs = {k: v for k, v in args.items() if k in export_args}  # quantize is passed explicitly below
        return benchmark(
            model=self,
            data=data,  # if no 'data' argument passed set data=None for default datasets
            imgsz=args["imgsz"],
            device=args["device"],
            verbose=verbose,
            format=format,
            quantize=args.get("quantize"),
            nms=args.get("nms"),
            **export_kwargs,
        )

    def export(
        self,
        **kwargs: Any,
    ) -> str:
        """Export the model to a different format suitable for deployment.

        This method facilitates the export of the model to various formats (e.g., ONNX, TorchScript) for deployment
        purposes. It uses the 'Exporter' class for the export process, combining model-specific overrides, method
        defaults, and any additional arguments provided.

        Args:
            **kwargs (Any): Arbitrary keyword arguments for export configuration. Common options include:
                - format (str): Export format (e.g., 'onnx', 'engine', 'coreml').
                - quantize (int | str): Precision, e.g. 16 (FP16) or 8 (INT8); 32/None is FP32.
                - device (str): Device to run the export on.
                - workspace (int): Maximum memory workspace size for TensorRT engines.
                - nms (bool | None): None for raw output, True to embed NMS, or False for the NMS-free head where available.
                - simplify (bool): Simplify ONNX model.

        Returns:
            (str): The path to the exported model file.

        Raises:
            TypeError: If the model is not a PyTorch model.
            ValueError: If an unsupported export format is specified.
            RuntimeError: If the export process fails due to errors.

        Examples:
            >>> model = YOLO("yolo26n.pt")
            >>> model.export(format="onnx", dynamic=True, simplify=True)
            'path/to/exported/model.onnx'
        """
        self._check_is_pytorch_model()
        from .exporter import Exporter, export_formats

        custom = {
            "imgsz": self.model.args["imgsz"],
            "batch": 1,
            "data": None,
            "device": None,  # reset to avoid multi-GPU errors
            "verbose": False,
        }  # method defaults
        args = {**self.overrides, **custom, **kwargs, "mode": "export"}  # highest priority args on the right
        try:
            return Exporter(overrides=args, _callbacks=self.callbacks)(model=self.model)
        except Exception:
            formats = export_formats()
            export_format = args.get("format", DEFAULT_CFG_DICT["format"])
            format_name = dict(zip(formats["Argument"], formats["Format"])).get(
                str(export_format).lower(), export_format
            )
            LOGGER.info(f"Export to {format_name} in the cloud with Ultralytics Platform: {PLATFORM_URL}")
            raise

    def train(
        self,
        trainer=None,
        **kwargs: Any,
    ):
        """Train the model using the specified dataset and training configuration.

        This method facilitates model training with a range of customizable settings. It supports training with a custom
        trainer or the default training approach. The method handles scenarios such as resuming training from a
        checkpoint and updating model and configuration after training. It checks for pip updates and combines default
        configurations, method-specific defaults, and user-provided arguments to configure the training process.

        Args:
            trainer (type[BaseTrainer], optional): Custom trainer class for model training. If None, uses the default
                trainer for the task.
            **kwargs (Any): Arbitrary keyword arguments for training configuration. Common options include:
                - data (str): Path to dataset configuration file.
                - epochs (int): Number of training epochs.
                - batch (int): Batch size for training.
                - imgsz (int): Input image size.
                - device (str): Device to run training on (e.g., 'cuda', 'cpu').
                - workers (int): Number of dataloader worker processes.
                - optimizer (str): Optimizer to use for training.
                - lr0 (float): Initial learning rate.
                - patience (int): Epochs to wait for no observable improvement for early stopping of training.
                - augmentations (list[Callable]): List of augmentation functions to apply during training.

        Returns:
            (ultralytics.utils.metrics.DetMetrics | dict | None): Training metrics if available and training is
                successful; otherwise, None. The specific metrics type depends on the task. When `data` is a list or
                tuple of datasets, the base model is fine-tuned on each in series and a {dataset: metrics} dict is
                returned.

        Raises:
            TypeError: If the model is not a PyTorch model.
            FileNotFoundError: If training completes but no checkpoint was saved.

        Examples:
            >>> model = YOLO("yolo26n.pt")
            >>> results = model.train(data="coco8.yaml", epochs=3)
            >>> multi = model.train(data=["coco8.yaml", "african-wildlife.yaml"], epochs=3)  # fine-tune across datasets
        """
        self._check_is_pytorch_model()
        checks.check_pip_update_available()

        overrides = YAML.load(checks.check_yaml(kwargs["cfg"])) if kwargs.get("cfg") else self.overrides
        custom = {
            # NOTE: handle the case when 'cfg' includes 'data'.
            "data": (overrides.get("data") if kwargs.get("cfg") else None)
            or DEFAULT_CFG_DICT["data"]
            or (
                None if isinstance(kwargs.get("resume", overrides.get("resume")), (str, Path)) else TASK2DATA[self.task]
            ),
            "model": self.overrides["model"],
            "task": self.task,
        }  # method defaults
        args = {**overrides, **custom, **kwargs, "mode": "train"}  # prioritizes rightmost args
        pretrained = args.setdefault("pretrained", self.overrides.get("pretrained", True))
        if isinstance(args.get("data"), (list, tuple)):  # fine-tune a single base model across multiple datasets
            from ultralytics.engine.trainer import MultiTrainer

            use_python_trainer = trainer is not None or self.callbacks != callbacks.get_default_callbacks()
            self.trainer = MultiTrainer(
                (trainer or self._smart_load("trainer")) if use_python_trainer else None,
                args,
                self.model,
                _callbacks=self.callbacks,
            )
            self.metrics = self.trainer.train()
            return self.metrics
        if args.get("resume") is True:  # resume=True (boolean) uses current model as checkpoint
            if self.ckpt and self.ckpt.get("epoch", -1) >= 0 and self.ckpt.get("optimizer") is not None:
                args["resume"], args["data"] = self.ckpt_path, kwargs.get("data") or overrides.get("data")
            else:
                LOGGER.warning(
                    f"model '{self.ckpt_path}' is not a resumable training checkpoint "
                    f"(missing epoch/optimizer state). Use 'resume' only to continue incomplete training. "
                    f"Starting new training instead."
                )
                args["resume"] = False

        self.trainer = (trainer or self._smart_load("trainer"))(overrides=args, _callbacks=self.callbacks)
        if not args.get("resume") and self.ckpt:
            # Reuse the already-loaded checkpoint model to avoid re-resolving remote weight sources during trainer setup.
            weights = None if pretrained is False else self.model
            if isinstance(pretrained, (str, Path)):
                weights, _ = load_checkpoint(pretrained)
            self.trainer.model = self.trainer.get_model(weights=weights, cfg=self.model.yaml)
            self.model = self.trainer.model
            self.predictor = None  # this module replaced the one the cached predictor wrapped

        self.trainer.train()
        # Update model and cfg after training
        if RANK in {-1, 0}:
            ckpt = self.trainer.best if self.trainer.best.exists() else self.trainer.last
            if not ckpt.exists():
                raise FileNotFoundError(
                    f"Training completed but no checkpoint was saved. Expected {self.trainer.best} or {self.trainer.last}."
                )
            self.model, self.ckpt = load_checkpoint(ckpt)
            self.predictor = None  # the checkpoint replaced the module again; covers resume and YAML runs too
            self.overrides = self._reset_ckpt_args(self.model.args)
            self.overrides["model"] = str(ckpt)  # the reset drops it, train() and tune() read it back
            self.metrics = getattr(self.trainer.validator, "metrics", None)
            if self.metrics is None and self.ckpt:  # recover from checkpoint under DDP (validator runs in subprocess)
                self.metrics = self.ckpt.get("train_metrics")
        return self.metrics

    def tune(
        self,
        use_ray=False,
        iterations=300,
        *args: Any,
        **kwargs: Any,
    ):
        """Conduct hyperparameter tuning for the model, with an option to use Ray Tune.

        This method supports two modes of hyperparameter tuning: using Ray Tune or a custom tuning method. When Ray Tune
        is enabled, it leverages the 'run_ray_tune' function from the ultralytics.utils.tuner module. Otherwise, it uses
        the internal 'Tuner' class for tuning. The method combines default, overridden, and custom arguments to
        configure the tuning process.

        Args:
            use_ray (bool): Whether to use Ray Tune for hyperparameter tuning. If False, uses internal tuning method.
            iterations (int): Number of tuning iterations to perform.
            *args (Any): Positional arguments forwarded to Ray Tune's `run_ray_tune` (space, grace_period,
                gpu_per_trial) when `use_ray=True`; the internal Tuner raises a TypeError if any are given.
            **kwargs (Any): Additional keyword arguments for tuning configuration. These are combined with model
                overrides and defaults to configure the tuning process.

        Returns:
            (ray.tune.ResultGrid | None): When use_ray=True, returns a ResultGrid with hyperparameter search results.
                When use_ray=False, returns None and saves best hyperparameters to YAML.

        Raises:
            TypeError: If the model is not a PyTorch model, or positional args are passed with use_ray=False.

        Examples:
            >>> model = YOLO("yolo26n.pt")
            >>> results = model.tune(data="coco8.yaml", iterations=5)
            >>> print(results)

            # Use Ray Tune for more advanced hyperparameter search
            >>> results = model.tune(use_ray=True, iterations=20, data="coco8.yaml")
        """
        self._check_is_pytorch_model()
        if "optimizer" not in (kwargs.get("space") or {}):
            kwargs.setdefault("optimizer", "AdamW")
        if use_ray:
            from ultralytics.utils.tuner import run_ray_tune

            return run_ray_tune(self, *args, iterations=iterations, **kwargs)
        else:
            if args:
                raise TypeError(f"Positional arguments {args} require use_ray=True, pass Tuner options as keywords.")
            from .tuner import Tuner

            custom = {}  # method defaults
            args = {**self.overrides, **custom, **kwargs, "mode": "train"}  # highest priority args on the right
            return Tuner(args=args, model=self.model, _callbacks=self.callbacks)(iterations=iterations)

    def _apply(self, fn) -> Model:
        """Apply a function to model parameters, buffers, and tensors.

        This method extends the functionality of the parent class's _apply method by additionally updating the device
        in the model's overrides and dropping the cached predictor when a model tensor is converted. No-op tensor
        conversions preserve the predictor.

        Args:
            fn (Callable): A function to be applied to the model's tensors. This is typically a method like to(), cpu(),
                cuda(), half(), or float().

        Returns:
            (Model): The model instance with the function applied and updated attributes.

        Raises:
            TypeError: If the model is not a PyTorch model.

        Examples:
            >>> model = Model("yolo26n.pt")
            >>> model = model._apply(lambda t: t.cuda())  # Move model to GPU
        """
        self._check_is_pytorch_model()

        def apply(t):
            converted = fn(t)
            if converted is not t:
                self.predictor = None  # predictor owns a copy, including buffers and detection-head tensors
            return converted

        super()._apply(apply)
        self.overrides["device"] = self.device  # was str(self.device) i.e. device(type='cuda', index=0) -> 'cuda:0'
        return self

    @property
    def names(self) -> dict[int, str]:
        """Retrieve the class names associated with the loaded model.

        This property returns the class names if they are defined in the model. It checks the class names for validity
        using the 'check_class_names' function from the ultralytics.nn.autobackend module. If the predictor is not
        initialized, it sets it up before retrieving the names.

        Returns:
            (dict[int, str]): A dictionary of class names associated with the model, where keys are class indices and
                values are the corresponding class names.

        Raises:
            AttributeError: If the model or predictor does not have a 'names' attribute.

        Examples:
            >>> model = YOLO("yolo26n.pt")
            >>> print(model.names)
            {0: 'person', 1: 'bicycle', 2: 'car', ...}
        """
        from ultralytics.nn.autobackend import check_class_names

        if hasattr(self.model, "names"):
            return check_class_names(self.model.names)
        if not self.predictor:  # export formats will not have predictor defined until predict() is called
            predictor = self._smart_load("predictor")(overrides=self.overrides, _callbacks=self.callbacks)
            predictor.setup_model(model=self.model, verbose=False)  # do not mess with self.predictor.model args
            return predictor.model.names
        return self.predictor.model.names

    @property
    def device(self) -> torch.device:
        """Get the device on which the model's parameters are allocated.

        This property determines the device (CPU or GPU) where the model's parameters are currently stored. It is
        applicable only to models that are instances of torch.nn.Module.

        Returns:
            (torch.device | None): The device (CPU/GPU) of the model, or None if the model is not a torch.nn.Module
                instance.

        Examples:
            >>> model = YOLO("yolo26n.pt")
            >>> print(model.device)
            device(type='cuda', index=0)  # if CUDA is available
            >>> model = model.to("cpu")
            >>> print(model.device)
            device(type='cpu')
        """
        return next(self.model.parameters()).device if isinstance(self.model, torch.nn.Module) else None

    @property
    def transforms(self):
        """Retrieve the transformations applied to the input data of the loaded model.

        This property returns the transformations if they are defined in the model. The transforms typically include
        preprocessing steps like resizing, normalization, and data augmentation that are applied to input data before it
        is fed into the model.

        Returns:
            (object | None): The transform object of the model if available, otherwise None.

        Examples:
            >>> model = YOLO("yolo26n.pt")
            >>> transforms = model.transforms
            >>> if transforms:
            ...     print(f"Model transforms: {transforms}")
            ... else:
            ...     print("No transforms defined for this model.")
        """
        return self.model.transforms if hasattr(self.model, "transforms") else None

    def add_callback(self, event: str, func) -> None:
        """Add a callback function for a specified event.

        This method allows registering custom callback functions that are triggered on specific events during model
        operations such as training or inference. Callbacks provide a way to extend and customize the behavior of the
        model at various stages of its lifecycle.

        Args:
            event (str): The name of the event to attach the callback to. Must be a valid event name recognized by the
                Ultralytics framework.
            func (Callable): The callback function to be registered. This function will be called when the specified
                event occurs.

        Examples:
            >>> def on_train_start(trainer):
            ...     print("Training is starting!")
            >>> model = YOLO("yolo26n.pt")
            >>> model.add_callback("on_train_start", on_train_start)
            >>> model.train(data="coco8.yaml", epochs=1)
        """
        self.callbacks[event].append(func)

    def clear_callback(self, event: str) -> None:
        """Clear all callback functions registered for a specified event.

        This method removes all custom and default callback functions associated with the given event. It resets the
        callback list for the specified event to an empty list, effectively removing all registered callbacks for that
        event.

        Args:
            event (str): The name of the event for which to clear the callbacks. This should be a valid event name
                recognized by the Ultralytics callback system.

        Examples:
            >>> model = YOLO("yolo26n.pt")
            >>> model.add_callback("on_train_start", lambda trainer: print("Training started"))
            >>> model.clear_callback("on_train_start")
            >>> # All callbacks for 'on_train_start' are now removed

        Notes:
            - This method affects both custom callbacks added by the user and default callbacks
              provided by the Ultralytics framework.
            - After calling this method, no callbacks will be executed for the specified event
              until new ones are added.
            - Use with caution as it removes all callbacks, including essential ones that might
              be required for proper functioning of certain operations.
        """
        self.callbacks[event] = []

    def reset_callbacks(self) -> None:
        """Reset all callbacks to their default functions.

        This method reinstates the default callback functions for all events, removing any custom callbacks that were
        previously added. It iterates through all default callback events and replaces the current callbacks with the
        default ones.

        The default callbacks are defined in the 'callbacks.default_callbacks' dictionary, which contains predefined
        functions for various events in the model's lifecycle, such as on_train_start, on_epoch_end, etc.

        This method is useful when you want to revert to the original set of callbacks after making custom
        modifications, ensuring consistent behavior across different runs or experiments.

        Examples:
            >>> model = YOLO("yolo26n.pt")
            >>> model.add_callback("on_train_start", custom_function)
            >>> model.reset_callbacks()
            # All callbacks are now reset to their default functions
        """
        for event in callbacks.default_callbacks:
            self.callbacks[event] = [callbacks.default_callbacks[event][0]]

    @staticmethod
    def _reset_ckpt_args(args: dict[str, Any]) -> dict[str, Any]:
        """Reset specific arguments when loading a PyTorch model checkpoint.

        This method filters the input arguments dictionary to retain only a specific set of keys that are considered
        important for model loading. It's used to ensure that only relevant arguments are preserved when loading a model
        from a checkpoint, discarding any unnecessary or potentially conflicting settings.

        Args:
            args (dict[str, Any]): A dictionary containing various model arguments and settings.

        Returns:
            (dict[str, Any]): A new dictionary containing only the specified include keys from the input arguments.

        Examples:
            >>> original_args = {"imgsz": 640, "data": "coco.yaml", "task": "detect", "batch": 16, "epochs": 100}
            >>> reset_args = Model._reset_ckpt_args(original_args)
            >>> print(reset_args)
            {'imgsz': 640, 'data': 'coco.yaml', 'task': 'detect'}
        """
        include = {"imgsz", "data", "task", "single_cls"}  # only remember these arguments when loading a PyTorch model
        return {k: v for k, v in args.items() if k in include}

    def _val_data(self) -> str | None:
        """Return the checkpoint's dataset when it still resolves, otherwise the task default."""
        data = self.overrides.get("data")
        if checks.check_data_portable(data):
            return data
        if not isinstance(data, (str, Path)):  # absent, or a YOLOE multi-source training dict
            data = None
        default = TASK2DATA.get(self.task)
        LOGGER.warning(
            f"Checkpoint dataset '{data}' was not found. Using default 'data={default}'. Pass data=... to validate on "
            f"the original dataset."
            if data
            else f"'data' argument is missing. Using default 'data={default}'."
        )
        return default

    def _smart_load(self, key: str):
        """Intelligently load the appropriate module based on the model task.

        This method dynamically selects and returns the correct module (model, trainer, validator, or predictor) based
        on the current task of the model and the provided key. It uses the task_map dictionary to determine the
        appropriate module to load for the specific task.

        Args:
            key (str): The type of module to load. Must be one of 'model', 'trainer', 'validator', or 'predictor'.

        Returns:
            (object): The loaded module class corresponding to the specified key and current task.

        Raises:
            NotImplementedError: If the specified key is not supported for the current task.

        Examples:
            >>> model = YOLO("yolo26n.pt")
            >>> predictor_class = model._smart_load("predictor")
            >>> trainer_class = model._smart_load("trainer")
        """
        try:
            return self.task_map[self.task][key]
        except Exception as e:
            name = self.__class__.__name__
            mode = inspect.stack()[1][3]  # get the function name.
            raise NotImplementedError(f"'{name}' model does not support '{mode}' mode for '{self.task}' task.") from e

    @property
    def task_map(self) -> dict:
        """Provide a mapping from model tasks to corresponding classes for different modes.

        This property method returns a dictionary that maps each supported task (e.g., detect, segment, semantic,
        classify) to a nested dictionary. The nested dictionary contains mappings for different operational modes
        (model, trainer, validator, predictor) to their respective class implementations.

        The mapping allows for dynamic loading of appropriate classes based on the model's task and the desired
        operational mode. This facilitates a flexible and extensible architecture for handling various tasks and modes
        within the Ultralytics framework.

        Returns:
            (dict[str, dict[str, Any]]): A dictionary mapping task names to nested dictionaries. Each nested dictionary
                contains mappings for 'model', 'trainer', 'validator', and 'predictor' keys to their respective class
                implementations for that task.

        Examples:
            >>> model = YOLO("yolo26n.pt")
            >>> task_map = model.task_map
            >>> detect_predictor = task_map["detect"]["predictor"]
            >>> segment_trainer = task_map["segment"]["trainer"]
        """
        raise NotImplementedError("Please provide task map for your model!")

    def eval(self):
        """Set the model to evaluation mode.

        This method changes the model's mode to evaluation, which affects layers like dropout and batch normalization
        that behave differently during training and evaluation. In evaluation mode, these layers use running statistics
        rather than computing batch statistics, and dropout layers are disabled.

        Returns:
            (Model): The model instance with evaluation mode set.

        Examples:
            >>> model = YOLO("yolo26n.pt")
            >>> model.eval()
            >>> # Model is now in evaluation mode for inference
        """
        self.model.eval()
        return self

    def __getattr__(self, name):
        """Enable accessing model attributes directly through the Model class.

        This method provides a way to access attributes of the underlying model directly through the Model class
        instance. It first checks if the requested attribute is 'model', in which case it returns the model from
        the module dictionary. Otherwise, it delegates the attribute lookup to the underlying model.

        Args:
            name (str): The name of the attribute to retrieve.

        Returns:
            (Any): The requested attribute value.

        Raises:
            AttributeError: If the requested attribute does not exist in the model.

        Examples:
            >>> model = YOLO("yolo26n.pt")
            >>> print(model.stride)  # Access model.stride attribute
            >>> print(model.names)  # Access model.names attribute
        """
        return self._modules["model"] if name == "model" else getattr(self.model, name)
