576, 256, 256])
[DEBUG] Decoder stage 2
Using encoder feature at index 1
x.shape after concat at stage 2: torch.Size([4, 424, 512, 512])
[DEBUG] Decoder stage 3
Using encoder feature at index 0
encoder_feature.shape after resizing at stage 3: torch.Size([4, 24, 1024, 1024])
x.shape after concat at stage 3: torch.Size([4, 280, 1024, 1024])
[DEBUG] feature_maps in FPNFuse: type=<class 'list'>
[DEBUG] Valid feature_maps[0] shape: torch.Size([4, 512, 128, 128])
[DEBUG] Valid feature_maps[1] shape: torch.Size([4, 384, 256, 256])
[DEBUG] Valid feature_maps[2] shape: torch.Size([4, 256, 512, 512])
[DEBUG] Valid feature_maps[3] shape: torch.Size([4, 128, 1024, 1024])
yolo_output shape: torch.Size([4, 21, 128, 128])
objectness_map shape: torch.Size([4, 1, 256, 256])
vessel_map shape: torch.Size([4, 1, 256, 256])
fishing_map shape: torch.Size([4, 1, 256, 256])
size_map shape: torch.Size([4, 1, 256, 256])
offset_map shape: torch.Size([4, 2, 256, 256])
yolo_output shape: torch.Size([4, 21, 128, 128])
classification_output shape: torch.Size([4, 2, 128, 128])
yolo_output upsampled to: torch.Size([4, 21, 256, 256])
classification_output upsampled to: torch.Size([4, 2, 256, 256])
[2024-09-30 15:56:59,873][__main__][ERROR] - Expected keys 'CENTERNET_OUTPUT_YOLO' and 'CENTERNET_TARGET_YOLO' not found in model outputs or encoded targets.
DEBUG:rasterio.env:Entering env context: <rasterio.env.Env object at 0x349219100>
DEBUG:rasterio.env:Starting outermost env
DEBUG:rasterio.env:No GDAL environment exists
DEBUG:rasterio.env:New GDAL environment <rasterio._env.GDALEnv object at 0x3492190d0> created
DEBUG:rasterio._env:GDAL_DATA found in environment.
DEBUG:rasterio._env:PROJ_DATA found in environment.
DEBUG:rasterio._env:Started GDALEnv: self=<rasterio._env.GDALEnv object at 0x3492190d0>.
DEBUG:rasterio.env:Entered env context: <rasterio.env.Env object at 0x349219100>
DEBUG:rasterio._base:Sharing flag: 0
DEBUG:rasterio._base:Nodata success: 1, Nodata value: -32768.000000
DEBUG:rasterio._base:Dataset <open DatasetReader name='/Volumes/Crucial/xview3_dataset/full_dataset/validation/4a97701b4bd81bf7v/VH_dB.tif' mode='r'> is started.
DEBUG:rasterio.env:Exiting env context: <rasterio.env.Env object at 0x349219100>
DEBUG:rasterio.env:Cleared existing <rasterio._env.GDALEnv object at 0x3492190d0> options
DEBUG:rasterio._env:Stopped GDALEnv <rasterio._env.GDALEnv object at 0x3492190d0>.
DEBUG:rasterio.env:Exiting outermost env
DEBUG:rasterio.env:Exited env context: <rasterio.env.Env object at 0x349219100>
DEBUG:rasterio.env:Entering env context: <rasterio.env.Env object at 0x3491e32e0>
DEBUG:rasterio.env:Starting outermost env
DEBUG:rasterio.env:No GDAL environment exists
DEBUG:rasterio.env:New GDAL environment <rasterio._env.GDALEnv object at 0x3492190d0> created
DEBUG:rasterio._env:GDAL_DATA found in environment.
DEBUG:rasterio._env:PROJ_DATA found in environment.
DEBUG:rasterio._env:Started GDALEnv: self=<rasterio._env.GDALEnv object at 0x3492190d0>.
DEBUG:rasterio.env:Entered env context: <rasterio.env.Env object at 0x3491e32e0>
DEBUG:rasterio._io:Output nodata value read from file: -32768.0
DEBUG:rasterio._io:Output nodata values: [-32768.0]
DEBUG:rasterio._io:all_valid: False
DEBUG:rasterio._io:mask_flags: ([<MaskFlags.nodata: 8>],)
DEBUG:rasterio._io:Jump straight to _read()
DEBUG:rasterio._io:Window: Window(col_off=4096, row_off=6144, width=2048, height=2048)
DEBUG:rasterio._io:IO window xoff=4096.0 yoff=6144.0 width=2048.0 height=2048.0
No scenes to process for predictions.
DEBUG:rasterio.env:Exiting env context: <rasterio.env.Env object at 0x3491e32e0>
DEBUG:rasterio.env:Cleared existing <rasterio._env.GDALEnv object at 0x3492190d0> options
DEBUG:rasterio._env:Stopped GDALEnv <rasterio._env.GDALEnv object at 0x3492190d0>.
DEBUG:rasterio.env:Exiting outermost env
DEBUG:rasterio.env:Exited env context: <rasterio.env.Env object at 0x3491e32e0>
DEBUG:rasterio.env:Entering env context: <rasterio.env.Env object at 0x349219550>
DEBUG:rasterio.env:Starting outermost env
DEBUG:rasterio.env:No GDAL environment exists
DEBUG:rasterio.env:New GDAL environment <rasterio._env.GDALEnv object at 0x349219340> created
DEBUG:rasterio._env:GDAL_DATA found in environment.
DEBUG:rasterio._env:PROJ_DATA found in environment.
DEBUG:rasterio._env:Started GDALEnv: self=<rasterio._env.GDALEnv object at 0x349219340>.
DEBUG:rasterio.env:Entered env context: <rasterio.env.Env object at 0x349219550>
DEBUG:rasterio._base:Sharing flag: 0
DEBUG:rasterio._base:Nodata success: 1, Nodata value: -32768.000000
DEBUG:rasterio._base:Dataset <open DatasetReader name='/Volumes/Crucial/xview3_dataset/full_dataset/validation/4a97701b4bd81bf7v/VV_dB.tif' mode='r'> is started.
DEBUG:rasterio.env:Exiting env context: <rasterio.env.Env object at 0x349219550>
DEBUG:rasterio.env:Cleared existing <rasterio._env.GDALEnv object at 0x349219340> options
DEBUG:rasterio._env:Stopped GDALEnv <rasterio._env.GDALEnv object at 0x349219340>.
DEBUG:rasterio.env:Exiting outermost env
DEBUG:rasterio.env:Exited env context: <rasterio.env.Env object at 0x349219550>
DEBUG:rasterio.env:Entering env context: <rasterio.env.Env object at 0x3491e32e0>
DEBUG:rasterio.env:Starting outermost env
DEBUG:rasterio.env:No GDAL environment exists
DEBUG:rasterio.env:New GDAL environment <rasterio._env.GDALEnv object at 0x349219340> created
DEBUG:rasterio._env:GDAL_DATA found in environment.
DEBUG:rasterio._env:PROJ_DATA found in environment.

let's see some files to findout where's the issue  if nothing cause issues in these files till me to show you other files

#multilabel_trimmed_loss.py

from functools import partial
from typing import Dict, Tuple

import torch
from pytorch_toolbelt.losses import SoftBCEWithLogitsLoss, BinaryBiTemperedLogisticLoss, BinaryFocalLoss, BalancedBCEWithLogitsLoss
from torch import nn, Tensor
from torch.nn import functional as F

from .functional import binary_shannon_entropy_loss
from ..constants import (
    CENTERNET_OUTPUT_SIZE,
    CENTERNET_OUTPUT_OFFSET,
    CENTERNET_OUTPUT_OBJECTNESS_MAP,
    CENTERNET_TARGET_SIZE,
    CENTERNET_TARGET_OFFSET,
    CENTERNET_TARGET_FISHING_MAP,
    CENTERNET_OUTPUT_FISHING_MAP,
    CENTERNET_OUTPUT_VESSEL_MAP,
    CENTERNET_TARGET_VESSEL_MAP,
    CENTERNET_TARGET_OBJECTNESS_MAP,
    CENTERNET_OUTPUT_YOLO,
    CENTERNET_TARGET_YOLO
)
from ...constants import IGNORE_LABEL

__all__ = ["MultilabelCenterTrimmedLoss"]


class MultilabelCenterTrimmedLoss(nn.Module):
    fraction: int
    samplewise: bool
    regularize_ignored: bool

    def __init__(
        self,
        fraction: int,
        samplewise: bool = True,
        objectness_loss: str = "rfl",
        classifier_loss="bce",
        size_loss: str = "mse",
        offset_loss: str = "mse",
        regularize_ignored: bool = False,
        regularization_weight: float = 1.0,
        ignore_index: int = IGNORE_LABEL,
    ):
        super().__init__()
        self.fraction = fraction
        self.objectness_loss = objectness_loss
        self.samplewise = samplewise
        self.regularize_ignored = regularize_ignored
        self.regularization_weight = regularization_weight
        self.ignore_index = ignore_index

        if objectness_loss == "rfl":
            self.objectness_loss = partial(self.reduced_focal_loss, alpha=2, beta=4, eps=0)
        elif objectness_loss == "l1":
            self.objectness_loss = self.objectness_l1_loss
        elif objectness_loss == "mse":
            self.objectness_loss = self.objectness_mse_loss
        elif objectness_loss == "focal_mse":
            self.objectness_loss = self.objectness_focal_mse_loss
        else:
            raise KeyError(objectness_loss)

        if classifier_loss == "bce":
            self.classifier_loss = SoftBCEWithLogitsLoss(ignore_index=ignore_index, reduction="none")
        elif classifier_loss == "bbce":
            self.classifier_loss = BalancedBCEWithLogitsLoss(ignore_index=ignore_index, reduction="none")
        elif classifier_loss == "soft_bce":
            self.classifier_loss = SoftBCEWithLogitsLoss(ignore_index=ignore_index, reduction="none", smooth_factor=0.05)
        elif classifier_loss == "bitempered":
            self.classifier_loss = BinaryBiTemperedLogisticLoss(t1=0.5, t2=4.0, ignore_index=ignore_index, reduction="none")
        elif classifier_loss == "focal":
            self.classifier_loss = BinaryFocalLoss(ignore_index=ignore_index, alpha=None, reduction="none")
        else:
            raise KeyError(classifier_loss)

        if size_loss == "huber":
            self.size_loss = self.huber_loss
        elif size_loss == "mse":
            self.size_loss = self.mse_loss
        elif size_loss == "smooth_l1":
            self.size_loss = self.smooth_l1_loss
        elif size_loss == "rel_error":
            self.size_loss = self.relative_length_error
        else:
            raise KeyError(size_loss)

        if offset_loss == "mse":
            self.offset_loss = self.mse_loss
        elif offset_loss == "l1":
            self.offset_loss = self.l1_loss
        elif offset_loss == "smooth_l1":
            self.offset_loss = self.smooth_l1_loss
        elif offset_loss == "huber":
            self.offset_loss = self.huber_loss
        elif offset_loss is None:
            self.offset_loss = None
        else:
            raise KeyError(offset_loss)

    def objectness_mse_loss(self, pred: Tensor, gt: Tensor, pos_mask) -> Tuple[Tensor, Tensor]:
        ign_mask = gt.eq(self.ignore_index)
        pt = pred.sigmoid()
        neg_mask = ~pos_mask
        loss = F.mse_loss(pt, gt, reduction="none")
        pos_loss: Tensor = loss * pos_mask
        neg_loss: Tensor = loss * neg_mask
        return torch.masked_fill(pos_loss, ign_mask, 0), torch.masked_fill(neg_loss, ign_mask, 0)

    def objectness_focal_mse_loss(self, pred: Tensor, gt: Tensor, pos_mask: Tensor, alpha=2.0, beta=2.0) -> Tuple[Tensor, Tensor]:
        pt = pred.sigmoid()
        neg_mask = ~pos_mask
        loss = F.mse_loss(pt, gt, reduction="none")
        pos_loss: Tensor = loss * pos_mask
        neg_loss: Tensor = loss * neg_mask * torch.pow(pt, alpha) * torch.pow(1.0 - gt, beta)
        return pos_loss, neg_loss

    def objectness_l1_loss(self, pred: Tensor, gt: Tensor, pos_mask):
        pt = pred.sigmoid()
        ign_mask = gt.eq(self.ignore_index)
        neg_mask = ~pos_mask
        loss = F.l1_loss(pt, gt, reduction="none")
        pos_loss: Tensor = loss * pos_mask
        neg_loss: Tensor = (loss * neg_mask) ** 4
        return torch.masked_fill(pos_loss, ign_mask, 0), torch.masked_fill(neg_loss, ign_mask, 0)

    def reduced_focal_loss(self, pred: Tensor, gt: Tensor, pos_mask, alpha: float, beta: float, eps: float = 1e-4):
        neg_mask = ~pos_mask
        ign_mask = gt.eq(self.ignore_index)

        pt = pred.sigmoid().clamp(eps, 1 - eps)

        pos_loss: Tensor = -torch.pow(1 - pt, alpha) * F.logsigmoid(pred) * pos_mask
        neg_loss: Tensor = -torch.pow(1.0 - gt, beta) * torch.pow(pt, alpha) * F.logsigmoid(-pred) * neg_mask

        return torch.masked_fill(pos_loss, ign_mask, 0), torch.masked_fill(neg_loss, ign_mask, 0)

    def mse_loss(self, prediction, target, pos_mask):
        loss = F.mse_loss(prediction, target, reduction="none") * pos_mask
        return loss

    def l1_loss(self, prediction, target, pos_mask):
        loss = F.l1_loss(prediction, target, reduction="none") * pos_mask
        return loss

    def smooth_l1_loss(self, prediction, target, pos_mask):
        loss = F.smooth_l1_loss(prediction, target, reduction="none") * pos_mask
        return loss

    def huber_loss(self, prediction, target, pos_mask):
        loss = F.huber_loss(prediction, target, reduction="none", delta=1) * pos_mask
        return loss

    def relative_length_error(self, prediction, target, pos_mask, eps=1e-4):
        loss = pos_mask * F.smooth_l1_loss(prediction, target, reduction="none") / (target + eps)
        return loss

    # @torch.cuda.amp.autocast(False)
    def forward(self, **kwargs) -> Dict[str, Tensor]:
        gt_objectness: Tensor = kwargs[CENTERNET_TARGET_OBJECTNESS_MAP]
        gt_is_vessel: Tensor = kwargs[CENTERNET_TARGET_VESSEL_MAP]
        gt_is_fishing: Tensor = kwargs[CENTERNET_TARGET_FISHING_MAP]
        gt_size: Tensor = kwargs[CENTERNET_TARGET_SIZE]
        gt_yolo: Tensor = kwargs[CENTERNET_TARGET_YOLO] 

        pred_objectness: Tensor = kwargs[CENTERNET_OUTPUT_OBJECTNESS_MAP]
        pred_is_vessel: Tensor = kwargs[CENTERNET_OUTPUT_VESSEL_MAP]
        pred_is_fishing: Tensor = kwargs[CENTERNET_OUTPUT_FISHING_MAP]
        pred_size: Tensor = kwargs[CENTERNET_OUTPUT_SIZE]
        pred_yolo: Tensor = kwargs[CENTERNET_OUTPUT_YOLO]

        peaks_mask = gt_objectness.eq(1)
        true_vessels_mask = gt_is_vessel.eq(1)

        num_objects = peaks_mask.sum(dtype=torch.float32).clamp_min(1.0)
        num_vessels = (true_vessels_mask & peaks_mask).sum(dtype=torch.float32).clamp_min(1.0)

        uncertain_objectness_mask = gt_objectness.eq(self.ignore_index)
        certain_objectness_mask = ~uncertain_objectness_mask

        # Returns unreduced losses
        pos_objectness_loss, neg_objectness_loss = self.objectness_loss(pred_objectness, gt_objectness, pos_mask=peaks_mask)

        # Weight classification loss by objectness (This make sense for target encoding=center).
        # This way pixels closer to the center of the keypoint will have bigger influence than ones on the periphery
        vessel_loss = self.classifier_loss(pred_is_vessel, gt_is_vessel) * gt_objectness * certain_objectness_mask
        fishing_loss = self.classifier_loss(pred_is_fishing, gt_is_fishing) * gt_objectness * certain_objectness_mask
        yolo_loss = self.yolo_loss(pred_yolo, gt_yolo)
        
        if self.regularize_ignored:
            uncertain_vessel_mask = gt_is_vessel.eq(self.ignore_index)
            uncertain_fishing_mask = gt_is_fishing.eq(self.ignore_index)
            # Plus regularization loss for ignored values in the objectness heatmap
            objectness_reg_loss = binary_shannon_entropy_loss(pred_objectness) * uncertain_objectness_mask

            vsl_reg_loss = binary_shannon_entropy_loss(pred_is_vessel) * certain_objectness_mask * uncertain_vessel_mask * gt_objectness
            fsh_reg_loss = (
                binary_shannon_entropy_loss(pred_is_fishing)
                * certain_objectness_mask
                * uncertain_fishing_mask
                * true_vessels_mask
                * gt_objectness
            )

            pos_objectness_loss += objectness_reg_loss * self.regularization_weight
            vessel_loss += vsl_reg_loss * self.regularization_weight
            fishing_loss += fsh_reg_loss * self.regularization_weight

        valid_size_mask = peaks_mask * (gt_size > 0)  # Size loss only for positive lengths
        num_objects_with_length = valid_size_mask.sum(dtype=torch.float32).clamp_min(1.0)
        size_loss = self.size_loss(pred_size, gt_size, valid_size_mask)

        if self.offset_loss is not None:
            offset_loss = self.offset_loss(kwargs[CENTERNET_OUTPUT_OFFSET], kwargs[CENTERNET_TARGET_OFFSET], peaks_mask).sum(
                dim=1, keepdim=True
            )
        else:
            offset_loss = torch.zeros_like(size_loss)

        if self.fraction > 0:
            if self.samplewise:
                (pos_objectness_loss, neg_objectness_loss, vessel_loss, fishing_loss, offset_loss, size_loss,) = samplewise_trimming(
                    pred_objectness,
                    pred_is_vessel,
                    pred_is_fishing,
                    pos_objectness_loss,
                    neg_objectness_loss,
                    vessel_loss,
                    fishing_loss,
                    offset_loss,
                    size_loss,
                )
            else:
                (pos_objectness_loss, neg_objectness_loss, vessel_loss, fishing_loss, offset_loss, size_loss,) = batchwise_trimming(
                    pred_objectness,
                    pred_is_vessel,
                    pred_is_fishing,
                    pos_objectness_loss,
                    neg_objectness_loss,
                    vessel_loss,
                    fishing_loss,
                    offset_loss,
                    size_loss,
                )

        mean_obj_loss = (neg_objectness_loss.sum(dtype=torch.float32) + pos_objectness_loss.sum(dtype=torch.float32)) / num_objects
        mean_vessel_loss = (vessel_loss.sum(dtype=torch.float32)) / num_objects
        mean_fishing_loss = (fishing_loss.sum(dtype=torch.float32)) / num_vessels
        mean_siz_loss = size_loss.sum(dtype=torch.float32) / num_objects_with_length

        losses = {
            "objectness": mean_obj_loss.type_as(pred_objectness),
            "vessel": mean_vessel_loss.type_as(pred_objectness),
            "fishing": mean_fishing_loss.type_as(pred_objectness),
            "size": mean_siz_loss.type_as(pred_objectness),
        }
        if self.offset_loss is not None:
            mean_off_loss = offset_loss.sum(dtype=torch.float32) / num_objects
            losses["offset"] = mean_off_loss.type_as(pred_objectness)
        return losses


def samplewise_trimming(
    pred_objectness,
    pred_is_vessel,
    pred_is_fishing,
    pos_objectness_loss,
    neg_objectness_loss,
    vessel_loss,
    fishing_loss,
    offset_loss,
    size_loss,
    num_elements_to_ignore,
):
    pred_objectness = pred_objectness.flatten(2)  # [B, 1, N]
    pred_is_vessel = pred_is_vessel.flatten(2)  # [B, 1, N]
    pred_is_fishing = pred_is_fishing.flatten(2)  # [B, 1, N]
    pos_objectness_loss = pos_objectness_loss.flatten(2)  # [B, 1, N]
    neg_objectness_loss = neg_objectness_loss.flatten(2)  # [B, 1, N]
    vessel_loss = vessel_loss.flatten(2)  # [B, 1, N]
    fishing_loss = fishing_loss.flatten(2)  # [B, 1, N]
    offset_loss = offset_loss.flatten(2)  # [B, 1, N]
    size_loss = size_loss.flatten(2)  # [B, 1, N]

    # Here we ignore top-N negative losses for negative objectness loss (E.g target is predicted, but groundtruth is negative).
    # Since some objects may be missing in gt, and we don't want to include those wrong false-positives in the training signal
    # And use keep indexes to propagate ignored pixels in other losses
    _, indices = torch.topk(neg_objectness_loss, k=num_elements_to_ignore, dim=2, largest=True, sorted=False)

    non_ignored_mask = torch.ones_like(neg_objectness_loss).scatter_(2, indices, 0)

    # Instead of just setting loss to zeros in those hard example predictions, we minimize entropy loss
    # for objectness & classification to push the predictions to either 0 or 1
    neg_objectness_loss = neg_objectness_loss * non_ignored_mask + binary_shannon_entropy_loss(pred_objectness) * (1 - non_ignored_mask)
    vessel_loss = vessel_loss * non_ignored_mask + binary_shannon_entropy_loss(pred_is_vessel) * (1 - non_ignored_mask)
    fishing_loss = fishing_loss * non_ignored_mask + binary_shannon_entropy_loss(pred_is_fishing) * (1 - non_ignored_mask)
    # For offset & size there is no regularization, so we exclude corresponding pixels from the grad. contribution
    offset_loss = offset_loss * non_ignored_mask
    size_loss = size_loss * non_ignored_mask

    return (
        pos_objectness_loss,
        neg_objectness_loss,
        vessel_loss,
        fishing_loss,
        offset_loss,
        size_loss,
    )


def batchwise_trimming(
    pred_objectness,
    pred_is_vessel,
    pred_is_fishing,
    pos_objectness_loss,
    neg_objectness_loss,
    vessel_loss,
    fishing_loss,
    offset_loss,
    size_loss,
    num_elements_to_ignore,
):
    pred_objectness = pred_objectness.permute(1, 0, 2, 3).flatten(1)  # [1, BxN]
    pred_is_vessel = pred_is_vessel.permute(1, 0, 2, 3).flatten(1)  # [C, BxN]
    pred_is_fishing = pred_is_fishing.permute(1, 0, 2, 3).flatten(1)  # [C, BxN]
    pos_objectness_loss = pos_objectness_loss.permute(1, 0, 2, 3).flatten(1)  # [1, BxN]
    neg_objectness_loss = neg_objectness_loss.permute(1, 0, 2, 3).flatten(1)  # [1, BxN]
    vessel_loss = vessel_loss.permute(1, 0, 2, 3).flatten(1)  # [1, BxN]
    fishing_loss = fishing_loss.permute(1, 0, 2, 3).flatten(1)  # [1, BxN]
    offset_loss = offset_loss.permute(1, 0, 2, 3).flatten(1)  # [1, BxN]
    size_loss = size_loss.permute(1, 0, 2, 3).flatten(1)  # [1, BxN]

    # Here we ignore top-N negative losses for negative objectness loss (E.g target is predicted, but groundtruth is negative).
    # Since some objects may be missing in gt, and we don't want to include those wrong false-positives in the training signal
    # And use keep indexes to propagate ignored pixels in other losses

    _, indices = torch.topk(neg_objectness_loss, k=num_elements_to_ignore, dim=1, largest=True, sorted=False)

    non_ignored_mask = torch.ones_like(neg_objectness_loss).scatter_(1, indices, 0)

    # Instead of just setting loss to zeros in those hard example predictions, we minimize entropy loss
    # for objectness & classification to push the predictions to either 0 or 1
    neg_objectness_loss = neg_objectness_loss * non_ignored_mask + binary_shannon_entropy_loss(pred_objectness) * (1 - non_ignored_mask)
    vessel_loss = vessel_loss * non_ignored_mask + binary_shannon_entropy_loss(pred_is_vessel) * (1 - non_ignored_mask)
    fishing_loss = fishing_loss * non_ignored_mask + binary_shannon_entropy_loss(pred_is_fishing) * (1 - non_ignored_mask)
    # For offset & size there is no regularization, so we exclude corresponding pixels from the grad. contribution
    offset_loss = offset_loss * non_ignored_mask
    size_loss = size_loss * non_ignored_mask

    return (
        pos_objectness_loss,
        neg_objectness_loss,
        vessel_loss,
        fishing_loss,
        offset_loss,
        size_loss,
    )

#next file is circlet_loss.py

from functools import partial
from typing import Dict, List, Tuple

import torch
from pytorch_toolbelt.losses import SoftBCEWithLogitsLoss, BinaryBiTemperedLogisticLoss, BinaryFocalLoss, BalancedBCEWithLogitsLoss
from torch import nn, Tensor
from torch.nn import functional as F

from .functional import binary_shannon_entropy_loss, objectness_reduced_focal_loss, objectness_mse_loss
from ..constants import (
    CENTERNET_OUTPUT_SIZE,
    CENTERNET_OUTPUT_OFFSET,
    CENTERNET_OUTPUT_OBJECTNESS_MAP,
    CENTERNET_TARGET_SIZE,
    CENTERNET_TARGET_OFFSET,
    CENTERNET_TARGET_FISHING_MAP,
    CENTERNET_OUTPUT_FISHING_MAP,
    CENTERNET_OUTPUT_VESSEL_MAP,
    CENTERNET_TARGET_VESSEL_MAP,
    CENTERNET_TARGET_OBJECTNESS_MAP,
    CENTERNET_TARGET_YOLO,
    CENTERNET_OUTPUT_YOLO
)
from ...constants import IGNORE_LABEL, TARGET_SAMPLE_WEIGHT

__all__ = ["CircleNetLoss"]
from xview3.utils import TorchDevice
device = TorchDevice.choose_torch_device()

class CircleNetLoss(nn.Module):
    def __init__(self, objectness_loss: str = "rfl", classifier_loss: str = "bce", size_loss: str = "mse", offset_loss: str = "mse", regularize_ignored: bool = False, regularization_weight: float = 1.0, ignore_index: int = IGNORE_LABEL, num_classes: int = 20, anchors: list = [[10, 13], [16, 30], [33, 23]]):
        super().__init__()
        self.ignore_index = ignore_index  # Ensure ignore_index is set
        self.objectness_loss = partial(objectness_mse_loss, ignore_index=ignore_index) if objectness_loss == "mse" else partial(objectness_reduced_focal_loss, ignore_index=ignore_index)
        self.classifier_loss = SoftBCEWithLogitsLoss(ignore_index=ignore_index, reduction="none")
        self.size_loss = mse_loss if size_loss == "mse" else smooth_l1_loss
        self.offset_loss = mse_loss if offset_loss == "mse" else l1_loss
        self.regularize_ignored = regularize_ignored
        self.regularization_weight = regularization_weight

        if objectness_loss == "rfl":
            self.objectness_loss = partial(objectness_reduced_focal_loss, ignore_index=self.ignore_index, alpha=2, beta=4, eps=0)
        elif objectness_loss == "mse":
            self.objectness_loss = partial(objectness_mse_loss, ignore_index=self.ignore_index)
        else:
            raise KeyError(objectness_loss)

        if classifier_loss == "bce":
            self.classifier_loss = SoftBCEWithLogitsLoss(ignore_index=ignore_index, reduction="none")
        elif classifier_loss == "bbce":
            self.classifier_loss = BalancedBCEWithLogitsLoss(ignore_index=ignore_index, reduction="none")
        elif classifier_loss == "soft_bce":
            self.classifier_loss = SoftBCEWithLogitsLoss(ignore_index=ignore_index, reduction="none", smooth_factor=0.05)
        elif classifier_loss == "bitempered":
            self.classifier_loss = BinaryBiTemperedLogisticLoss(t1=0.5, t2=4.0, ignore_index=ignore_index, reduction="none")
        elif classifier_loss == "focal":
            self.classifier_loss = BinaryFocalLoss(ignore_index=ignore_index, alpha=None, reduction="none")
        else:
            raise KeyError(classifier_loss)

        if size_loss == "huber":
            self.size_loss = huber_loss
        elif size_loss == "mse":
            self.size_loss = mse_loss
        elif size_loss == "smooth_l1":
            self.size_loss = smooth_l1_loss
        elif size_loss == "rel_error":
            self.size_loss = relative_length_error
        else:
            raise KeyError(size_loss)

        if offset_loss == "mse":
            self.offset_loss = mse_loss
        elif offset_loss == "l1":
            self.offset_loss = l1_loss
        elif offset_loss == "smooth_l1":
            self.offset_loss = smooth_l1_loss
        elif offset_loss == "huber":
            self.offset_loss = huber_loss
        elif offset_loss is None:
            self.offset_loss = None
        else:
            raise KeyError(offset_loss)

        # Initialize YOLO loss module
        self.yolo_loss_module = YOLOLoss(num_classes=num_classes, anchors=anchors)

    def forward(self, **kwargs) -> Dict[str, Tensor]:
        gt_objectness: Tensor = kwargs[CENTERNET_TARGET_OBJECTNESS_MAP]
        gt_is_vessel: Tensor = kwargs[CENTERNET_TARGET_VESSEL_MAP]
        gt_is_fishing: Tensor = kwargs[CENTERNET_TARGET_FISHING_MAP]
        gt_size: Tensor = kwargs[CENTERNET_TARGET_SIZE]
        gt_offset: Tensor = kwargs.get(CENTERNET_TARGET_OFFSET, torch.zeros_like(gt_size))
        pred_size: Tensor = kwargs[CENTERNET_OUTPUT_SIZE]

        if pred_size.shape[-2:] != gt_size.shape[-2:]:
            pred_size = F.interpolate(pred_size, size=gt_size.shape[-2:], mode='bilinear', align_corners=False)

        # Initialize sample_weights with the correct shape
        batch_size = gt_objectness.size(0)
        sample_weights = kwargs.get(
            TARGET_SAMPLE_WEIGHT,
            torch.ones(batch_size, 1, gt_size.shape[2], gt_size.shape[3], device=gt_objectness.device)
        )

        pred_objectness: Tensor = kwargs.get(CENTERNET_OUTPUT_OBJECTNESS_MAP, torch.zeros_like(gt_objectness))
        pred_is_vessel: Tensor = kwargs.get(CENTERNET_OUTPUT_VESSEL_MAP, torch.zeros_like(gt_is_vessel))
        pred_is_fishing: Tensor = kwargs.get(CENTERNET_OUTPUT_FISHING_MAP, torch.zeros_like(gt_is_fishing))
        pred_size: Tensor = kwargs.get(CENTERNET_OUTPUT_SIZE, torch.zeros_like(gt_size))
        pred_offset: Tensor = kwargs.get(CENTERNET_OUTPUT_OFFSET, torch.zeros_like(gt_offset))

        # Ensure pred_is_vessel has the same shape as gt_is_vessel by upsampling if necessary
        if pred_is_vessel.shape[-2:] != gt_is_vessel.shape[-2:]:
            pred_is_vessel = F.interpolate(pred_is_vessel, size=gt_is_vessel.shape[-2:], mode='bilinear', align_corners=False)

        # Ensure pred_is_fishing has the same shape as gt_is_fishing by upsampling if necessary
        if pred_is_fishing.shape[-2:] != gt_is_fishing.shape[-2:]:
            pred_is_fishing = F.interpolate(pred_is_fishing, size=gt_is_fishing.shape[-2:], mode='bilinear', align_corners=False)

        if pred_objectness.shape[-2:] != gt_objectness.shape[-2:]:
            pred_objectness = F.interpolate(pred_objectness, size=gt_objectness.shape[-2:], mode='bilinear', align_corners=False)

        # Mask and loss calculation
        peaks_mask = gt_objectness.eq(1)
        true_vessels_mask = gt_is_vessel.eq(1)
        num_objects = peaks_mask.sum(dtype=torch.float32).clamp_min(1.0)
        num_vessels = (true_vessels_mask & peaks_mask).sum(dtype=torch.float32).clamp_min(1.0)

        uncertain_objectness_mask = gt_objectness.eq(self.ignore_index)
        certain_objectness_mask = ~uncertain_objectness_mask

        # Compute classification losses
        vessel_loss = self.classifier_loss(pred_is_vessel, gt_is_vessel) * gt_objectness * certain_objectness_mask
        fishing_loss = self.classifier_loss(pred_is_fishing, gt_is_fishing) * gt_objectness * certain_objectness_mask
        pos_objectness_loss, neg_objectness_loss = self.objectness_loss(pred_objectness, gt_objectness, pos_mask=peaks_mask)

        if self.regularize_ignored:
            uncertain_vessel_mask = gt_is_vessel.eq(self.ignore_index)
            uncertain_fishing_mask = (gt_is_fishing.eq(self.ignore_index) & gt_is_vessel.eq(1)) | uncertain_vessel_mask
            # Plus regularization loss for ignored values in the objectness heatmap
            obj_reg_loss = binary_shannon_entropy_loss(pred_objectness) * uncertain_objectness_mask

            vsl_reg_loss = binary_shannon_entropy_loss(pred_is_vessel) * uncertain_vessel_mask

            fsh_reg_loss = binary_shannon_entropy_loss(pred_is_fishing) * uncertain_fishing_mask

            obj_reg_loss = (
                obj_reg_loss.sum(dtype=torch.float32)
                * self.regularization_weight
                / uncertain_objectness_mask.sum(dtype=torch.float32).clamp_min(1.0)
            )

            vsl_reg_loss = (
                vsl_reg_loss.sum(dtype=torch.float32)
                * self.regularization_weight
                / uncertain_vessel_mask.sum(dtype=torch.float32).clamp_min(1.0)
            )
            fsh_reg_loss = (
                fsh_reg_loss.sum(dtype=torch.float32)
                * self.regularization_weight
                / uncertain_fishing_mask.sum(dtype=torch.float32).clamp_min(1.0)
            )
        else:
            obj_reg_loss = 0
            vsl_reg_loss = 0
            fsh_reg_loss = 0

        valid_size_mask = peaks_mask * (gt_size > 0)
        valid_size_mask = gt_size > 0  # Size loss only for positive lengths
        num_objects_with_length = valid_size_mask.sum(dtype=torch.float32).clamp_min(1.0)

        # Reshape size_loss to match the shape of sample_weights
        size_loss = self.size_loss(pred_size, gt_size, valid_size_mask)
        size_loss = size_loss.view_as(sample_weights)

        # Ensure sample_weights has the correct shape
        height, width = vessel_loss.shape[2], vessel_loss.shape[3]
        sample_weights = sample_weights.expand(-1, -1, height, width)

        print(f"vessel_loss shape: {vessel_loss.shape}")
        print(f"sample_weights shape: {sample_weights.shape}")
        print(f"size_loss shape: {size_loss.shape}")

        weighted_vessel_loss = sample_weights * vessel_loss
        weighted_fishing_loss = sample_weights * fishing_loss
        weighted_size_loss = sample_weights * size_loss

        vessel_loss_reduced = weighted_vessel_loss.sum(dim=[2, 3])  # Shape: [batch_size, 1]
        fishing_loss_reduced = weighted_fishing_loss.sum(dim=[2, 3])  # Shape: [batch_size, 1]
        size_loss_reduced = weighted_size_loss.sum(dim=[2, 3])  # Shape: [batch_size, 1]

        print(f"weighted_vessel_loss shape: {weighted_vessel_loss.shape}")
        print(f"weighted_fishing_loss shape: {weighted_fishing_loss.shape}")
        print(f"weighted_size_loss shape: {weighted_size_loss.shape}")

        objectness_loss = neg_objectness_loss + pos_objectness_loss
        reduced_obj_loss = objectness_loss.sum(dim=[2, 3])  # Shape: [batch_size, 1]

        # Ensure reduced_obj_loss has the same spatial dimensions as sample_weights
        reduced_obj_loss = reduced_obj_loss.unsqueeze(-1).unsqueeze(-1).expand_as(sample_weights)

        # Now calculate mean_obj_loss
        mean_obj_loss = (sample_weights * reduced_obj_loss).sum() / num_objects + obj_reg_loss

        mean_vessel_loss = weighted_vessel_loss.sum() / num_objects + vsl_reg_loss
        mean_fishing_loss = weighted_fishing_loss.sum() / num_vessels + fsh_reg_loss
        mean_size_loss = weighted_size_loss.sum() / num_objects_with_length

        # Assume yolo_output and yolo_targets are obtained correctly at the call site
        yolo_output = kwargs.get(CENTERNET_OUTPUT_YOLO)
        if isinstance(yolo_output, dict):
            yolo_output = yolo_output['output']  # Assuming 'output' key contains the tensor
        elif isinstance(yolo_output, list):
            yolo_output = torch.cat(yolo_output, dim=1)  # Assuming list contains tensors to be concatenated along channel axis

        yolo_targets = kwargs.get(CENTERNET_TARGET_YOLO)
        if isinstance(yolo_targets, dict):
            yolo_targets = yolo_targets['output']  # Assuming 'output' key contains the tensor
        elif isinstance(yolo_targets, list):
            yolo_targets = torch.cat(yolo_targets, dim=1)  # Assuming list contains tensors to be concatenated along channel axis

        if not isinstance(yolo_output, Tensor) or not isinstance(yolo_targets, Tensor):
            raise ValueError("YOLO outputs and targets must be tensors.")

        # The following line will now work, given the tensors are passed correctly
        if yolo_output.shape[1] != yolo_targets.shape[1]:
            raise ValueError("Channel mismatch: predictions and targets must have the same number of channels.")
        # Calculate YOLO loss using the YOLO loss module
        yolo_loss = self.yolo_loss_module(yolo_output, yolo_targets)

        losses = {
            "objectness": mean_obj_loss.type_as(pred_objectness),
            "vessel": mean_vessel_loss.type_as(pred_objectness),
            "fishing": mean_fishing_loss.type_as(pred_objectness),
            "size": mean_size_loss.type_as(pred_objectness),
            "vsl_reg_loss": vsl_reg_loss,
            "fsh_reg_loss": fsh_reg_loss,
            "obj_reg_loss": obj_reg_loss,
            "num_objects": num_objects,
            "num_vessels": num_vessels,
            "num_objects_with_length": num_objects_with_length,
            "yolo_loss": yolo_loss.type_as(pred_objectness),
        }

        if self.offset_loss is not None:
            offset_loss = self.offset_loss(pred_offset, gt_offset, peaks_mask).sum(
                dim=1, keepdim=True
            )
            mean_off_loss = offset_loss.sum(dtype=torch.float32) / num_objects
            losses["offset"] = mean_off_loss.type_as(pred_objectness)

        return losses


def mse_loss(prediction, target, pos_mask):
    """MSE loss with a mask for valid objects."""
    prediction = F.interpolate(prediction, size=target.shape[-2:], mode='bilinear', align_corners=False)
    loss = F.mse_loss(prediction, target, reduction="none") * pos_mask
    return loss


def l1_loss(prediction, target, pos_mask):
    loss = F.l1_loss(prediction, target, reduction="none") * pos_mask
    return loss


def smooth_l1_loss(prediction, target, pos_mask):
    loss = F.smooth_l1_loss(prediction, target, reduction="none") * pos_mask
    return loss


def huber_loss(prediction, target, pos_mask):
    loss = F.huber_loss(prediction, target, reduction="none", delta=1) * pos_mask
    return loss


def relative_length_error(prediction, target, pos_mask, eps=1e-4):
    loss = pos_mask * F.smooth_l1_loss(prediction, target, reduction="none") / (target + eps)
    return loss


class YOLOLoss(nn.Module):
    def __init__(self, num_classes: int, anchors: list):
        super(YOLOLoss, self).__init__()
        self.num_classes = num_classes
        self.anchors = anchors
        self.mse_loss = nn.MSELoss(reduction='sum')  # Consider using 'mean' if preferred
        self.bce_loss = nn.BCELoss(reduction='sum')  # Consider using 'mean' if preferred

    def calculate_iou(self, box1: torch.Tensor, box2: torch.Tensor) -> torch.Tensor:
        """Calculate Intersection Over Union (IoU) between two sets of boxes."""
        x1y1 = torch.max(box1[..., :2], box2[..., :2])
        x2y2 = torch.min(box1[..., 2:], box2[..., 2:])
        intersection = torch.clamp((x2y2 - x1y1), min=0).prod(-1)
        area_box1 = (box1[..., 2] - box1[..., 0]) * (box1[..., 3] - box1[..., 1])
        area_box2 = (box2[..., 2] - box2[..., 0]) * (box2[..., 3] - box2[..., 1])
        union = area_box1 + area_box2 - intersection
        return intersection / union

    def forward(self, yolo_pred: torch.Tensor, yolo_targets: torch.Tensor) -> torch.Tensor:
        # Ensure predictions and targets have the same number of channels
        if yolo_pred.shape[1] != yolo_targets.shape[1]:
            raise ValueError(f"Channel mismatch: predictions have {yolo_pred.shape[1]} channels, targets have {yolo_targets.shape[1]} channels.")
        
        # Ensure spatial dimensions match
        if yolo_pred.shape[-2:] != yolo_targets.shape[-2:]:
            yolo_pred = F.interpolate(yolo_pred, size=yolo_targets.shape[-2:], mode='bilinear', align_corners=False)

        # Extract bounding boxes, confidence, and class predictions from YOLO output
        bbox_pred = yolo_pred[:, 0:4, :, :]  # [batch, 4, H, W]
        conf_pred = yolo_pred[:, 4:5, :, :]  # [batch, 1, H, W]
        class_pred = yolo_pred[:, 5:, :, :]  # [batch, num_classes, H, W]

        bbox_targets = yolo_targets[:, 0:4, :, :]  # [batch, 4, H, W]
        conf_targets = yolo_targets[:, 4:5, :, :]  # [batch, 1, H, W]
        class_targets = yolo_targets[:, 5:, :, :]  # [batch, num_classes, H, W]

        # Compute losses: bbox (MSE), confidence (BCE), and class (BCE or CrossEntropy)
        bbox_loss = self.mse_loss(bbox_pred, bbox_targets)
        conf_loss = self.bce_loss(conf_pred, conf_targets)
        class_loss = self.bce_loss(class_pred, class_targets)

        # Combine losses with appropriate weighting
        total_loss = bbox_loss + conf_loss + class_loss

        return total_loss

#next file is decoupled_head.py

from collections import OrderedDict
from typing import Dict, Tuple, List

from pytorch_toolbelt.modules import ACT_RELU, get_activation_block, conv1x1
from torch import nn, Tensor

from ...constants import (
    CENTERNET_OUTPUT_VESSEL_MAP,
    CENTERNET_OUTPUT_FISHING_MAP,
    CENTERNET_OUTPUT_OBJECTNESS_MAP,
    CENTERNET_OUTPUT_SIZE,
    CENTERNET_OUTPUT_OFFSET,
    CENTERNET_OUTPUT_YOLO,
)

__all__ = ["DecoupledHeadGroupNormLateShuffle"]


class FeatureRecombination(nn.Module):
    def __init__(self, channels, modulating_features):
        super().__init__()
        self.project = conv1x1(modulating_features, channels)

    def forward(self, x, modulation):
        return x * self.project(modulation).sigmoid()


def build_regression_block(
    input_channels: int,
    embedding_size: int,
    dropout_rate: float,
    num_blocks: int,
    activation: str,
    extra_final_layers: List[Tuple[str, nn.Module]],
    num_groups=16,
):
    activation_block = get_activation_block(activation)

    layers = [
        (
            "project",
            nn.Conv2d(input_channels, embedding_size, kernel_size=(1, 1), padding=0, bias=False),
        ),
    ]

    for layer_index in range(num_blocks):
        layers += [
            (
                f"conv{layer_index}",
                nn.Conv2d(embedding_size, embedding_size, kernel_size=(3, 3), padding=1, bias=False),
            ),
            (f"bn{layer_index}", nn.GroupNorm(num_groups, embedding_size)),
            (f"act{layer_index}", activation_block(inplace=True)),
        ]
    layers += [
        ("drop", nn.Dropout2d(dropout_rate, inplace=False)),
        ("conv1", nn.Conv2d(embedding_size, embedding_size, kernel_size=(1, 1), padding=0, bias=False)),
    ] + extra_final_layers

    return nn.Sequential(OrderedDict(layers))


class DecoupledHeadGroupNormLateShuffle(nn.Module):
    upsample_factor: int = 2

    def __init__(
        self,
        channels: int,
        classifier_dim: int,
        dropout_rate=0.25,
        objectness_dim: int = 32,
        size_dim: int = 32,
        offset_dim: int = 32,
        num_blocks: int = 3,
        activation: str = ACT_RELU,
        num_groups=16,
        yolo_dim: int = 128,  # Add YOLO dimension
    ):
        super().__init__()
        self.objectness = build_regression_block(
            channels,
            embedding_size=objectness_dim,
            dropout_rate=dropout_rate,
            activation=activation,
            num_groups=num_groups,
            num_blocks=num_blocks,
            extra_final_layers=[
                (("shuffle"), nn.PixelShuffle(2)),
                ("final", nn.Conv2d(objectness_dim // 4, 1, kernel_size=(3, 3), padding=1)),
            ],
        )

        self.classifier = build_regression_block(
            channels,
            embedding_size=classifier_dim,
            dropout_rate=dropout_rate,
            num_groups=num_groups,
            activation=activation,
            num_blocks=num_blocks,
            extra_final_layers=[],
        )

        self.size = build_regression_block(
            channels,
            embedding_size=size_dim,
            dropout_rate=dropout_rate,
            activation=activation,
            num_groups=num_groups,
            num_blocks=num_blocks,
            extra_final_layers=[],
        )

        self.classifier_recombination = FeatureRecombination(classifier_dim, size_dim)
        self.classifier_up = nn.Sequential(
            OrderedDict(
                [
                    ("conv1", nn.Conv2d(classifier_dim, classifier_dim, kernel_size=(3, 3), padding=1)),
                    ("shuffle", nn.PixelShuffle(2)),
                    ("final", nn.Conv2d(classifier_dim // 4, 2, kernel_size=(3, 3), padding=1)),
                ]
            )
        )
        self.size_recombination = FeatureRecombination(size_dim, classifier_dim)
        self.size_up = nn.Sequential(
            OrderedDict(
                [
                    ("conv1", nn.Conv2d(size_dim, size_dim, kernel_size=(3, 3), padding=1)),
                    ("shuffle", nn.PixelShuffle(2)),
                    ("final", nn.Conv2d(size_dim // 4, 1, kernel_size=(3, 3), padding=1)),
                    ("activation", nn.Softplus()),
                ]
            )
        )

        if offset_dim:
            self.offset = build_regression_block(
                channels,
                embedding_size=offset_dim,
                dropout_rate=dropout_rate,
                activation=activation,
                num_groups=num_groups,
                num_blocks=num_blocks,
                extra_final_layers=[
                    (("shuffle"), nn.PixelShuffle(2)),
                    ("final", nn.Conv2d(offset_dim // 4, 2, kernel_size=(3, 3), padding=1)),
                ],
            )
        else:
            self.offset = None

        # Add YOLO head
        self.yolo_head = build_regression_block(
            channels,
            embedding_size=yolo_dim,
            dropout_rate=dropout_rate,
            activation=activation,
            num_groups=num_groups,
            num_blocks=num_blocks,
            extra_final_layers=[
                (("shuffle"), nn.PixelShuffle(2)),
                ("final", nn.Conv2d(yolo_dim // 4, 21, kernel_size=(3, 3), padding=1)),  # Assuming YOLO output has 21 channels
            ],
        )

        # Determine if offset is included
        if self.offset is not None:
            self.out_channels = 6  # Total output channels
        else:
            self.out_channels = 4  # Exclude offset channels

        self._init_weights()

    def _init_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight)
            elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)

        # self.classifier_up.final.bias.data.fill_(0)
        # self.objectness.final.bias.data.fill_(-4)

        self.objectness.final.bias.data.fill_(-5)
        self.classifier_up.final.bias.data.fill_(-1)
        self.size_up.final.bias.data.fill_(2)

    def forward(self, features: Tensor) -> Dict[str, Tensor]:
        class_features = self.classifier(features)
        length_features = self.size(features)

        new_class_features = self.classifier_recombination(class_features, length_features)
        new_length_features = self.size_recombination(length_features, class_features)

        predicted_classes = self.classifier_up(new_class_features)
        predicted_length = self.size_up(new_length_features)

        output = {
            CENTERNET_OUTPUT_OBJECTNESS_MAP: self.objectness(features),
            CENTERNET_OUTPUT_VESSEL_MAP: predicted_classes[:, 0:1],
            CENTERNET_OUTPUT_FISHING_MAP: predicted_classes[:, 1:2],
            CENTERNET_OUTPUT_SIZE: predicted_length,
            CENTERNET_OUTPUT_YOLO: self.yolo_head(features),  # Add YOLO output
        }
        if self.offset is not None:
            output[CENTERNET_OUTPUT_OFFSET] = self.offset(features).clip(0, 1)
        return output