DEBUG:rasterio._base:Nodata success: 1, Nodata value: -32768.000000
DEBUG:rasterio._base:Dataset <open DatasetReader name='/Volumes/Crucial/xview3_dataset/full_dataset/full/validation/487b4884f467c94av/VH_dB.tif' mode='r'> is started.
DEBUG:rasterio.env:Exiting env context: <rasterio.env.Env object at 0x312531ca0>
DEBUG:rasterio.env:Cleared existing <rasterio._env.GDALEnv object at 0x312533ee0> options
DEBUG:rasterio._env:Stopped GDALEnv <rasterio._env.GDALEnv object at 0x312533ee0>.
DEBUG:rasterio.env:Exiting outermost env
DEBUG:rasterio.env:Exited env context: <rasterio.env.Env object at 0x312531ca0>
DEBUG:rasterio.env:Entering env context: <rasterio.env.Env object at 0x312531af0>
DEBUG:rasterio.env:Starting outermost env
DEBUG:rasterio.env:No GDAL environment exists
DEBUG:rasterio.env:New GDAL environment <rasterio._env.GDALEnv object at 0x312533ee0> 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 0x312533ee0>.
DEBUG:rasterio.env:Entered env context: <rasterio.env.Env object at 0x312531af0>
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=6797, row_off=2076, width=512, height=512)
DEBUG:rasterio._io:IO window xoff=6797.0 yoff=2076.0 width=512.0 height=512.0
DEBUG:rasterio.env:Exiting env context: <rasterio.env.Env object at 0x312531af0>
DEBUG:rasterio.env:Cleared existing <rasterio._env.GDALEnv object at 0x312533ee0> options
DEBUG:rasterio._env:Stopped GDALEnv <rasterio._env.GDALEnv object at 0x312533ee0>.
DEBUG:rasterio.env:Exiting outermost env
DEBUG:rasterio.env:Exited env context: <rasterio.env.Env object at 0x312531af0>
DEBUG:rasterio.env:Entering env context: <rasterio.env.Env object at 0x312531b20>
DEBUG:rasterio.env:Starting outermost env
DEBUG:rasterio.env:No GDAL environment exists
DEBUG:rasterio.env:New GDAL environment <rasterio._env.GDALEnv object at 0x312533dc0> 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 0x312533dc0>.
DEBUG:rasterio.env:Entered env context: <rasterio.env.Env object at 0x312531b20>
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/full/validation/487b4884f467c94av/VV_dB.tif' mode='r'> is started.
DEBUG:rasterio.env:Exiting env context: <rasterio.env.Env object at 0x312531b20>
DEBUG:rasterio.env:Cleared existing <rasterio._env.GDALEnv object at 0x312533dc0> options
DEBUG:rasterio._env:Stopped GDALEnv <rasterio._env.GDALEnv object at 0x312533dc0>.
DEBUG:rasterio.env:Exiting outermost env
DEBUG:rasterio.env:Exited env context: <rasterio.env.Env object at 0x312531b20>
DEBUG:rasterio.env:Entering env context: <rasterio.env.Env object at 0x312531ca0>
DEBUG:rasterio.env:Starting outermost env
DEBUG:rasterio.env:No GDAL environment exists
DEBUG:rasterio.env:New GDAL environment <rasterio._env.GDALEnv object at 0x312533dc0> 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 0x312533dc0>.
DEBUG:rasterio.env:Entered env context: <rasterio.env.Env object at 0x312531ca0>
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=6797, row_off=2076, width=512, height=512)
DEBUG:rasterio._io:IO window xoff=6797.0 yoff=2076.0 width=512.0 height=512.0
DEBUG:rasterio.env:Exiting env context: <rasterio.env.Env object at 0x312531ca0>
DEBUG:rasterio.env:Cleared existing <rasterio._env.GDALEnv object at 0x312533dc0> options
DEBUG:rasterio._env:Stopped GDALEnv <rasterio._env.GDALEnv object at 0x312533dc0>.
DEBUG:rasterio.env:Exiting outermost env
DEBUG:rasterio.env:Exited env context: <rasterio.env.Env object at 0x312531ca0>
[DEBUG] Channel 'vh' image shape before transformations: (512, 512)
[DEBUG] Channel 'vh' image shape after individual_transforms: (512, 512)
[DEBUG] Channel 'vh' image shape after normalization: (512, 512)
[DEBUG] Channel 'vv' image shape before transformations: (512, 512)
[DEBUG] Channel 'vv' image shape after individual_transforms: (512, 512)
[DEBUG] Channel 'vv' image shape after normalization: (512, 512)
[DEBUG] Number of channels: 2
[DEBUG] Channel 0 shape: (512, 512)
[DEBUG] Channel 1 shape: (512, 512)
[DEBUG] Combined image shape after np.dstack: (512, 512, 2)
[DEBUG] Keypoints being passed to albumentations:
(483.0, 399.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(259.0, 274.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(269.0, 253.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(270.0, 267.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(264.0, 255.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(260.0, 257.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(266.0, 271.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(251.0, 266.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(239.0, 244.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(255.0, 259.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(462.0, 281.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(436.0, 270.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(262.0, 201.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(267.0, 233.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(228.0, 237.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(258.0, 210.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(251.0, 214.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(245.0, 218.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(239.0, 218.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(235.0, 222.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(231.0, 224.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(226.0, 226.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(230.0, 241.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(254.0, 213.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(274.0, 231.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(481.0, 224.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
(233.0, 246.0, 1.9463857412338257) [<class 'float'>, <class 'float'>, <class 'float'>]
(218.0, 231.0, 1.3381845951080322) [<class 'float'>, <class 'float'>, <class 'float'>]
Error executing job with overrides: ['+config=configs/multilabel_circle_net.yaml']
Traceback (most recent call last):
  File "/Users/samerkharboush/Downloads/SAR-MPS-OptunaNasFbnYoloTransformers/train_multilabel.py", line 529, in <module>
    main()
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/hydra/main.py", line 94, in decorated_main
    _run_hydra(
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/hydra/_internal/utils.py", line 394, in _run_hydra
    _run_app(
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/hydra/_internal/utils.py", line 457, in _run_app
    run_and_report(
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/hydra/_internal/utils.py", line 223, in run_and_report
    raise ex
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/hydra/_internal/utils.py", line 220, in run_and_report
    return func()
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/hydra/_internal/utils.py", line 458, in <lambda>
    lambda: hydra.run(
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/hydra/_internal/hydra.py", line 132, in run
    _ = ret.return_value
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/hydra/core/utils.py", line 260, in return_value
    raise self._return_value
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/hydra/core/utils.py", line 186, in run_job
    ret.return_value = task_function(task_cfg)
  File "/Users/samerkharboush/Downloads/SAR-MPS-OptunaNasFbnYoloTransformers/train_multilabel.py", line 525, in main
    CenternetPipeline(cfg).train()
  File "/Users/samerkharboush/Downloads/SAR-MPS-OptunaNasFbnYoloTransformers/train_multilabel.py", line 416, in train
    model = self.build_model(config, self.loaders)
  File "/Users/samerkharboush/Downloads/SAR-MPS-OptunaNasFbnYoloTransformers/train_multilabel.py", line 207, in build_model
    model = nas.search(config.nas.num_iterations, loaders['train'], loaders['valid'])
  File "/Users/samerkharboush/Downloads/SAR-MPS-OptunaNasFbnYoloTransformers/xview3/nas.py", line 36, in search
    scores = [self.evaluate_model(model, train_loader, val_loader) for model in population]
  File "/Users/samerkharboush/Downloads/SAR-MPS-OptunaNasFbnYoloTransformers/xview3/nas.py", line 36, in <listcomp>
    scores = [self.evaluate_model(model, train_loader, val_loader) for model in population]
  File "/Users/samerkharboush/Downloads/SAR-MPS-OptunaNasFbnYoloTransformers/xview3/nas.py", line 115, in evaluate_model
    outputs = model(inputs)
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1553, in _wrapped_call_impl
    return self._call_impl(*args, **kwargs)
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1562, in _call_impl
    return forward_call(*args, **kwargs)
  File "/Users/samerkharboush/Downloads/SAR-MPS-OptunaNasFbnYoloTransformers/xview3/centernet/models/centernet_unet.py", line 180, in forward
    neck_features = self.neck_conv(neck_features)
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1553, in _wrapped_call_impl
    return self._call_impl(*args, **kwargs)
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1562, in _call_impl
    return forward_call(*args, **kwargs)
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/torch/nn/modules/conv.py", line 458, in forward
    return self._conv_forward(input, self.weight, self.bias)
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/torch/nn/modules/conv.py", line 454, in _conv_forward
    return F.conv2d(input, weight, bias, self.stride,
RuntimeError: Given groups=1, weight of size [256, 1024, 1, 1], expected input[8, 1280, 256, 256] to have 1024 channels, but got 1280 channels instead
(sam3) samerkharboush@Samers-MBP SAR-MPS-OptunaNasFbnYoloTransformers %     


#centernet_unet.py

from functools import partial
from typing import List, Dict
from omegaconf import DictConfig
from pytorch_toolbelt.modules import (
    ACT_RELU,
    decoders as D,
    EncoderModule,
    ResidualDeconvolutionUpsample2d,
)
from torch import nn
import torch
import timm
from hydra.utils import instantiate
import random
import torch.nn.functional as F
from xview3.centernet.models.custom_encoder import CustomTimmEncoder
from .unet_blocks import IRBlock, get_unet_block, SqueezeExcitation
from .necks import FPNNeck
from xview3.nas import NAS as NeuralArchitectureSearch
from .heads.yolo_head import YOLOHead
from .transformer_classifier import ConvolutionalClassifier
from xview3.common import FilterResponseNormalization, ResidualBlock, FeaturePyramidNetwork
from ..constants import (
    CENTERNET_OUTPUT_CLASS_MAP,
    CENTERNET_OUTPUT_OBJECTNESS_MAP,
    CENTERNET_OUTPUT_OFFSET,
    CENTERNET_OUTPUT_SIZE,
    CENTERNET_OUTPUT_VESSEL_MAP,
    CENTERNET_OUTPUT_FISHING_MAP,
)
__all__ = ["CenterNetUNetModel"]

class CenterNetUNetModel(nn.Module):
    def __init__(
        self,
        encoder: EncoderModule,
        decoder_features: List[int],
        head: nn.Module,
        box_coder,
        neck_out_channels: int = 256,  # Add this parameter
        input_upsample_factor: int = 1,
        activation=ACT_RELU,
        num_extra_blocks=0,
        block_type="UnetBlock",
        upsample_type="bilinear",
        extra_block=partial(IRBlock, stride=2),
        num_classes=2,
        anchors=[(10, 13), (16, 30), (33, 23)],
        **kwargs
    ):
        super().__init__()
        self.output_stride = getattr(encoder, 'strides', [32])[0]
        self.initial_upsample = (
            nn.UpsamplingBilinear2d(scale_factor=input_upsample_factor)
            if input_upsample_factor != 1 else nn.Identity()
        )
        unet_block = get_unet_block(block_type, activation=activation)
        self.encoder = encoder
        self.extra_encoder_blocks = nn.ModuleList(
            [extra_block(encoder.channels[-1], encoder.channels[-1]) for _ in range(num_extra_blocks)]
        )
        upsample_block = {
            "nearest": nn.UpsamplingNearest2d,
            "bilinear": nn.UpsamplingBilinear2d,
            "rdtsc": ResidualDeconvolutionUpsample2d,
            "shuffle": nn.PixelShuffle,
        }[upsample_type]

        if hasattr(encoder, 'channels'):
            encoder_channels = list(encoder.channels)
        else:
            encoder_channels = [32, 24, 40, 112, 320]  # Default for EfficientNet
        encoder_channels += [encoder_channels[-1]] * num_extra_blocks

        #self.neck = FPNNeck(self.decoder_channels, neck_out_channels)
        #self.neck_conv = nn.Conv2d(self.neck_output_channels, neck_out_channels, kernel_size=1)

        self.head = head
        self.head_output_channels = head.out_channels  # Ensure your head module defines 'out_channels'

        # Ensure self.nas is a module that can be called during forward
        self.nas = NeuralArchitectureSearch(
            search_space={
                'num_layers': (2, 5),
                'num_filters': (32, 128),
                'kernel_size': (3, 5),
                'activation': ['relu', 'leaky_relu']
            },
            evaluation_function=self.evaluate_architecture
        )
        self.nas_output_channels = self.nas.output_channels  # Define this in your NAS module

        self.yolo_head = YOLOHead(num_classes=num_classes, anchors=anchors)
        self.yolo_output_channels = self.yolo_head.num_anchors * (num_classes + 5)  # Adjust as per YOLO implementation

        self.classifier = ConvolutionalClassifier(in_channels=neck_out_channels, num_classes=num_classes)
        self.classifier_output_channels = self.classifier.out_channels
        self.box_coder = box_coder

        # Now define the fusion layer with known channel sizes
        total_input_channels = (
            self.head_output_channels +
            self.nas_output_channels +
            self.yolo_output_channels +
            self.classifier_output_channels
        )
        self.fusion = nn.Conv2d(total_input_channels, self.head_output_channels, kernel_size=1)

        self.dropout = nn.Dropout(p=0.5)
        #self.batch_norm = FilterResponseNormalization(256)
        #self.batch_norm = FilterResponseNormalization(neck_out_channels * len(self.decoder_channels))
        # Corrected code
        self.decoder_channels = decoder_features
        self.num_decoded_features = len(encoder_channels) - 1  # The number of decoder stages
        #self.num_decoder_features_used_by_neck = self.num_decoded_features
        #self.neck_output_channels = neck_out_channels * self.num_decoder_features_used_by_neck
        self.neck_output_channels = neck_out_channels * self.num_decoded_features
        print(f"[DEBUG] neck_output_channels: {self.neck_output_channels}")
        num_decoder_stages = self.num_decoded_features
        if len(self.decoder_channels) < num_decoder_stages:
            # Extend decoder_channels by repeating the last value
            self.decoder_channels.extend([self.decoder_channels[-1]] * (num_decoder_stages - len(self.decoder_channels)))
        elif len(self.decoder_channels) > num_decoder_stages:
            # Trim decoder_channels to the required length
            self.decoder_channels = self.decoder_channels[:num_decoder_stages]
        self.decoder = D.UNetDecoder(
            encoder_channels,
            self.decoder_channels,
            unet_block=unet_block,
            upsample_block=upsample_block,
        )
        self.neck = FPNNeck(self.decoder_channels, neck_out_channels)
        self.neck_conv = nn.Conv2d(
            in_channels=self.neck_output_channels,
            out_channels=neck_out_channels,
            kernel_size=1
        )
        print(f"[DEBUG] neck_output_channels: {self.neck_output_channels}")

        self.batch_norm = FilterResponseNormalization(self.neck_output_channels)

    def evaluate_architecture(self, architecture):
        # Implement a simple evaluation function
        return random.random()  # Replace with actual evaluation logic

    def forward(self, x):
        x = self.initial_upsample(x)
        print(f"[DEBUG] x shape after initial_upsample: {x.shape}")
        feature_maps = self.encoder(x)
        print(f"[DEBUG] feature_maps type: {type(feature_maps)}")
        if isinstance(feature_maps, list):
            for idx, fmap in enumerate(feature_maps):
                print(f"[DEBUG] feature_maps[{idx}] shape: {fmap.shape}")
        else:
            print("[ERROR] feature_maps is not a list")
            print(f"feature_maps shape: {feature_maps.shape}")
            print(f"feature_maps type: {type(feature_maps)}")
            # Convert feature_maps to a list if needed
            feature_maps = [feature_maps]
        for extra_block in self.extra_encoder_blocks:
            feature_maps.append(extra_block(feature_maps[-1]))
        decoded_features = self.decoder(feature_maps)
        print(f"[DEBUG] decoded_features type: {type(decoded_features)}")
        if isinstance(decoded_features, list):
            for idx, fmap in enumerate(decoded_features):
                print(f"[DEBUG] decoded_features[{idx}] shape: {fmap.shape}")
        else:
            print("[ERROR] decoded_features is not a list")
            print(f"decoded_features shape: {decoded_features.shape}")
            print(f"decoded_features type: {type(decoded_features)}")
            # Convert decoded_features to a list if needed
            decoded_features = [decoded_features]
        neck_features = self.neck(decoded_features)
        print(f"[DEBUG] neck_features shape before batch_norm: {neck_features.shape}")
        print(f"[DEBUG] neck_features channels: {neck_features.size(1)}")
        print(f"[DEBUG] neck_features shape before batch_norm: {neck_features.shape}")
        neck_features = self.batch_norm(neck_features)
        print(f"[DEBUG] neck_features shape after batch_norm: {neck_features.shape}")
        neck_features = self.dropout(neck_features)
        neck_features = self.neck_conv(neck_features)

        #self.head.channels = self.nas_output_channels  # Assuming self.nas_output_channels is the correct channel size
        head_output = self.head(neck_features)
        nas_output = self.nas(neck_features)
        yolo_output = self.yolo_head(neck_features)
        classification_output = self.classifier(neck_features)

        # Fusion layer
        fused_output = self.fusion(torch.cat([head_output, nas_output, yolo_output, classification_output], dim=1))

        return {
            CENTERNET_OUTPUT_OBJECTNESS_MAP: fused_output[:, :1],
            CENTERNET_OUTPUT_VESSEL_MAP: fused_output[:, 1:2],
            CENTERNET_OUTPUT_FISHING_MAP: fused_output[:, 2:3],
            CENTERNET_OUTPUT_SIZE: fused_output[:, 3:4],
            CENTERNET_OUTPUT_OFFSET: fused_output[:, 4:6],
        }

    @classmethod
    def from_config(cls, **config):
        # Extract and pop the encoder configuration
        encoder_config = config.pop('encoder')
        num_channels = config.pop('num_channels', 3)
        model_name = encoder_config.get('model_name', 'tf_efficientnet_b5_ns')
        pretrained = encoder_config.get('pretrained', False)

        # Instantiate the custom encoder
        encoder_module = CustomTimmEncoder(
            model_name=model_name,
            num_channels=num_channels,
            features_only=True,
            pretrained=pretrained
        )

        # Pop other configurations
        input_upsample_factor = config.pop("input_upsample_factor", 1)
        activation = config.pop('activation', ACT_RELU)
        num_extra_blocks = config.pop('num_extra_blocks', 0)
        num_classes = config.pop("num_classes", 2)
        anchors = config.pop("anchors", [(10, 13), (16, 30), (33, 23)])

        # Pop decoder configuration
        decoder_config = config.pop('decoder')
        decoder_features = decoder_config['channels']
        block_type = decoder_config.get("block_type", "UnetBlock")
        upsample_type = decoder_config.get("upsample_type", "bilinear")

        neck_out_channels = config.get('neck_out_channels', 256)
        neck_output_channels = neck_out_channels * len(decoder_features)
        print(f"Neck output channels: {neck_output_channels}")

        # Instantiate head and box_coder
        head_config = config.pop('head')
        head = instantiate(head_config, channels=neck_output_channels)

        box_coder_config = config.pop('box_coder')
        box_coder = instantiate(
            box_coder_config,
            output_stride=encoder_module.strides[0] // (head.upsample_factor * input_upsample_factor),
        )

        # Now initialize the model instance with the remaining config
        return cls(
            encoder=encoder_module,
            decoder_features=decoder_features,
            head=head,
            box_coder=box_coder,
            neck_out_channels=neck_out_channels,
            input_upsample_factor=input_upsample_factor,
            activation=activation,
            block_type=block_type,
            upsample_type=upsample_type,
            num_extra_blocks=num_extra_blocks,
            num_classes=num_classes,
            anchors=anchors,
            **config
        )

def adjust_encoder_input_channels(encoder_module, in_chans):
    # Access the underlying encoder model
    if hasattr(encoder_module, 'encoder'):
        base_encoder = encoder_module.encoder
    else:
        base_encoder = encoder_module

    if hasattr(base_encoder, 'conv_stem'):
        old_conv = base_encoder.conv_stem
        print(f"[DEBUG] Before adjustment, encoder.conv_stem.in_channels: {old_conv.in_channels}")

        new_conv = nn.Conv2d(
            in_channels=in_chans,
            out_channels=old_conv.out_channels,
            kernel_size=old_conv.kernel_size,
            stride=old_conv.stride,
            padding=old_conv.padding,
            bias=old_conv.bias is not None
        )

        # Initialize new_conv weights
        with torch.no_grad():
            # Copy weights for existing channels
            num_existing_in_channels = min(old_conv.in_channels, in_chans)
            new_conv.weight[:, :num_existing_in_channels, ...] = old_conv.weight[:, :num_existing_in_channels, ...]
            if in_chans > old_conv.in_channels:
                # Initialize added channels
                torch.nn.init.kaiming_normal_(new_conv.weight[:, old_conv.in_channels:, ...])

        base_encoder.conv_stem = new_conv
        print(f"[DEBUG] After adjustment, encoder.conv_stem.in_channels: {base_encoder.conv_stem.in_channels}")
    else:
        raise NotImplementedError("Adjusting input channels for this encoder is not implemented.")
    return encoder_module

class EnhancedCenterNetUNet(nn.Module):
    def __init__(self, in_channels, num_classes):
        super().__init__()
        self.backbone = CenterNetUNetModel(in_channels, num_classes)
        self.fpn = FeaturePyramidNetwork([64, 128, 256, 512], 256)
        self.se_blocks = nn.ModuleList([SqueezeExcitation(c) for c in [64, 128, 256, 512]])

    def forward(self, x):
        features = self.backbone(x)
        for i, feature in enumerate(features):
            features[i] = self.se_blocks[i](feature)
        return self.fpn(features)

#necks.py

import collections
from typing import List, Dict
from pytorch_toolbelt.modules import FPNFuse
from torch import nn, Tensor

__all__ = ["TensorAtIndex", "PickTensors", "HyperColumnNeck"]


class TensorAtIndex(nn.Module):
    def __init__(self, channels: List[int], index: int = 0):
        super().__init__()
        self.index = index
        self.channels = channels[index]

    def forward(self, feature_maps: List[Tensor]) -> Tensor:
        return feature_maps[self.index]


class PickTensors(nn.Module):
    def __init__(self, channels: List[int], indexes: Dict[int, str]):
        super().__init__()
        self.indexes = indexes
        self.channels = channels[0]

    def forward(self, feature_maps: List[Tensor]) -> Dict[str, Tensor]:
        return collections.OrderedDict((name, feature_maps[index]) for index, name in self.indexes.items())


class HyperColumnNeck(nn.Module):
    def __init__(self, channels: List[int], dropout_rate=0.0):
        super().__init__()
        self.fuse = FPNFuse(align_corners=True)
        self.channels = sum(channels)
        self.dropout = nn.Dropout2d(p=dropout_rate, inplace=True)

    def forward(self, feature_maps: List[Tensor]) -> Tensor:
        return self.dropout(self.fuse(feature_maps))

class FPNNeck(nn.Module):
    def __init__(self, channels: List[int], out_channels: int):
        super().__init__()
        self.fpn = FPNFuse(channels, out_channels, mode='bilinear', align_corners=False)
        
    def forward(self, feature_maps: List[Tensor]) -> Tensor:
        print(f"[DEBUG] feature_maps in FPNNeck: type={type(feature_maps)}")
        if isinstance(feature_maps, list):
            for idx, fmap in enumerate(feature_maps):
                print(f"[DEBUG] feature_maps[{idx}] shape: {fmap.shape}")
        else:
            print("[ERROR] feature_maps is not a list in FPNNeck")
            print(f"feature_maps shape: {feature_maps.shape}")
            feature_maps = [feature_maps]
        return self.fpn(feature_maps)
    

#nas.py

import torch
import torch.nn as nn
import torch.nn.functional as F
import random
import numpy as np
from skopt import gp_minimize
from skopt.space import Real, Integer, Categorical
import math
import copy
from torch import Tensor
from torch.optim import Adam
from torch.utils.data import DataLoader
from xview3.centernet.bboxer.multilabel_circle_coder import MultilabelCircleNetCoder

class ProgressiveNAS(nn.Module):
    def __init__(self, base_model, search_space):
        super(ProgressiveNAS, self).__init__()
        self.base_model = base_model
        self.search_space = search_space
        self.box_coder = MultilabelCircleNetCoder(
            image_size=(1024, 1024),
            output_stride=2,
            max_objects=512,
            heatmap_encoding="umich",
            labels_encoding="circle",
            ignore_value=-1
        )
        self.population_size = 20
        self.tournament_size = 5
        self.output_channels = 256 

    def search(self, num_iterations, train_loader, val_loader):
        population = [self.mutate_model(self.base_model) for _ in range(self.population_size)]
        
        for _ in range(num_iterations):
            scores = [self.evaluate_model(model, train_loader, val_loader) for model in population]
            
            new_population = []
            for _ in range(self.population_size):
                parent1 = self.tournament_selection(population, scores)
                parent2 = self.tournament_selection(population, scores)
                child = self.crossover(parent1, parent2)
                child = self.mutate_model(child)
                new_population.append(child)
            
            population = new_population

        best_model = max(population, key=lambda model: self.evaluate_model(model, train_loader, val_loader))
        return best_model

    def tournament_selection(self, population, scores):
        tournament = random.sample(list(zip(population, scores)), self.tournament_size)
        return max(tournament, key=lambda x: x[1])[0]

    def crossover(self, parent1, parent2):
        child = copy.deepcopy(parent1)
        for name, param in child.named_parameters():
            if random.random() < 0.5:
                param.data = parent2.state_dict()[name].clone()
        return child

    def mutate_model(self, model):
        new_model = copy.deepcopy(model)
        for param in new_model.parameters():
            if random.random() < 0.1:
                param.data += torch.randn_like(param.data) * 0.1
        
        # Structure mutation
        if random.random() < 0.2:
            new_layer = nn.Linear(64, 64)  # Example layer, adjust as needed
            if hasattr(new_model, 'encoder') and hasattr(new_model.encoder, 'add_module'):
                new_model.encoder.add_module(f"extra_layer_{id(new_layer)}", new_layer)
            elif hasattr(new_model, 'decoder') and hasattr(new_model.decoder, 'add_module'):
                new_model.decoder.add_module(f"extra_layer_{id(new_layer)}", new_layer)
        
        return new_model

    def evaluate_model(self, model, train_loader, val_loader):
        # Set the model to evaluation mode
        model.eval()
        total_loss = 0.0
        total_correct = 0
        total_samples = 0

        with torch.no_grad():
            for batch_idx, batch in enumerate(train_loader):
                # Debug batch structure
                print(f"[DEBUG] Batch index: {batch_idx}")
                print(f"[DEBUG] Batch keys: {list(batch.keys())}")
                
                # Extract inputs
                inputs = batch.get('INPUT_IMAGE_KEY')
                if inputs is None:
                    raise ValueError("Batch does not contain 'INPUT_IMAGE_KEY'. Available keys: {}".format(list(batch.keys())))
                
                # Extract targets
                targets = {
                    'CENTERNET_TARGET_OBJECTNESS_MAP': batch.get('CENTERNET_TARGET_OBJECTNESS_MAP'),
                    'CENTERNET_TARGET_VESSEL_MAP': batch.get('CENTERNET_TARGET_VESSEL_MAP'),
                    'CENTERNET_TARGET_FISHING_MAP': batch.get('CENTERNET_TARGET_FISHING_MAP'),
                    'CENTERNET_TARGET_SIZE': batch.get('CENTERNET_TARGET_SIZE'),
                    'CENTERNET_TARGET_OFFSET': batch.get('CENTERNET_TARGET_OFFSET'),
                }
                # Ensure all targets are present
                if None in targets.values():
                    missing_keys = [k for k, v in targets.items() if v is None]
                    raise ValueError(f"Batch missing target keys: {missing_keys}")
                
                # Move data to device
                device = next(model.parameters()).device
                inputs = inputs.to(device)
                targets = {k: v.to(device) for k, v in targets.items()}

                # Proceed with evaluation
                outputs = model(inputs)
                loss = self.calculate_loss(outputs, targets)

                # Update metrics (This will depend on your task)
                total_loss += loss.item() * inputs.size(0)
                total_samples += inputs.size(0)
                # For object detection, calculating 'accuracy' might not be straightforward.
                # You may need to implement a custom metric, e.g., Average Precision.
                
        # Return evaluation score (e.g., total loss)
        average_loss = total_loss / total_samples
        return -average_loss  # Negative loss if you're maximizing the evaluation score
        
    def calculate_loss(self, outputs, targets):
        # Assuming outputs and targets are dictionaries with matching keys
        # You should define the loss functions for each output-target pair
        total_loss = 0

        # Heatmap loss (e.g., Focal Loss)
        heatmap_loss = self.focal_loss(outputs['heatmap'], targets['CENTERNET_TARGET_OBJECTNESS_MAP'])
        total_loss += heatmap_loss

        # Size loss (e.g., L1 Loss)
        size_loss = F.l1_loss(outputs['size'], targets['CENTERNET_TARGET_SIZE'])
        total_loss += size_loss

        # Offset loss (e.g., L1 Loss)
        offset_loss = F.l1_loss(outputs['offset'], targets['CENTERNET_TARGET_OFFSET'])
        total_loss += offset_loss

        return total_loss

    def focal_loss(self, pred, gt):
        # Implement Focal Loss as used in CenterNet
        pos_inds = gt.eq(1).float()
        neg_inds = gt.lt(1).float()
        neg_weights = torch.pow(1 - gt, 4)

        pred = torch.clamp(pred, 1e-6, 1 - 1e-6)
        pos_loss = -torch.log(pred) * torch.pow(1 - pred, 2) * pos_inds
        neg_loss = -torch.log(1 - pred) * torch.pow(pred, 2) * neg_weights * neg_inds

        num_pos  = pos_inds.float().sum()
        loss = (pos_loss + neg_loss).sum() / num_pos.clamp(min=1)
        return loss

class NAS(nn.Module):
    def __init__(self, search_space, evaluation_function):
        super(NAS, self).__init__()
        self.search_space = search_space
        self.evaluation_function = evaluation_function
        self.layers = nn.ModuleList()
        self.output_channels = None

        # Build the architecture based on the search space
        self.build_architecture()
    
    def build_architecture(self):
        # Sample architecture from the search space
        architecture = self.sample_architecture()

        # Extract parameters
        num_layers = int(architecture.get('num_layers', 2))
        num_filters = int(architecture.get('num_filters', 64))
        kernel_size = int(architecture.get('kernel_size', 3))
        activation = architecture.get('activation', 'relu')

        # Set input_channels (assuming neck_features has 256 channels)
        input_channels = 256

        # Build layers
        for i in range(num_layers):
            conv = nn.Conv2d(
                in_channels=input_channels,
                out_channels=num_filters,
                kernel_size=kernel_size,
                padding=kernel_size // 2,
                bias=False
            )
            bn = nn.BatchNorm2d(num_filters)
            act = nn.ReLU(inplace=True) if activation == 'relu' else nn.LeakyReLU(0.1, inplace=True)
            self.layers.append(nn.Sequential(conv, bn, act))
            input_channels = num_filters  # Update for next layer

        # Set output_channels based on the final layer
        self.output_channels = num_filters

    def sample_architecture(self):
        # For simplicity, we'll randomly sample from the search space
        architecture = {}
        for key, value_range in self.search_space.items():
            if isinstance(value_range, list):
                architecture[key] = random.choice(value_range)
            elif isinstance(value_range, tuple):
                architecture[key] = random.randint(*value_range)
        return architecture

    def forward(self, x):
        for layer in self.layers:
            x = layer(x)
        return x

    def search(self, num_iterations, train_loader, val_loader):
        population = self.initialize_population()
        
        for _ in range(num_iterations):
            scores = [self.evaluate_model(model, train_loader, val_loader) for model in population]
            best_model = population[np.argmax(scores)]
            
            new_population = [best_model]
            while len(new_population) < len(population):
                parent1, parent2 = self.selection(population, scores)
                child = self.crossover(parent1, parent2)
                child = self.mutate(child)
                new_population.append(child)
            
            population = new_population
        
        return population[np.argmax([self.evaluate_model(model, train_loader, val_loader) for model in population])]


    def apply_to_model(self, model, architecture):
        for name, module in model.named_modules():
            if isinstance(module, nn.Conv2d):
                if 'num_filters' in architecture:
                    in_channels = module.in_channels
                    out_channels = architecture['num_filters']
                    kernel_size = architecture.get('kernel_size', module.kernel_size)
                    new_conv = nn.Conv2d(in_channels, out_channels, kernel_size, padding=kernel_size[0]//2)
                    setattr(model, name, new_conv)
            elif isinstance(module, nn.ReLU):
                if architecture.get('activation') == 'leaky_relu':
                    setattr(model, name, nn.LeakyReLU())
        
        if 'num_layers' in architecture:
            current_layers = len(list(model.children()))
            if architecture['num_layers'] > current_layers:
                for _ in range(architecture['num_layers'] - current_layers):
                    model.add_module(f'extra_layer_{_}', nn.Linear(64, 64))  # Adjust as needed
            elif architecture['num_layers'] < current_layers:
                for _ in range(current_layers - architecture['num_layers']):
                    model = nn.Sequential(*list(model.children())[:-1])
        
        return model

# Example usage
base_model = nn.Sequential(
    nn.Conv2d(3, 64, 3, padding=1),
    nn.ReLU(),
    nn.Conv2d(64, 64, 3, padding=1),
    nn.ReLU(),
    nn.MaxPool2d(2)
)

search_space = {
    'num_layers': (2, 5),
    'num_filters': (32, 128),
    'kernel_size': (3, 5),
    'activation': ['relu', 'leaky_relu']
}

nas = ProgressiveNAS(base_model, search_space)
# Ensure to provide train_loader and val_loader when calling search
# best_model = nas.search(num_iterations=10, train_loader=train_loader, val_loader=val_loader)