

################################################################################
# Objects365 V2 · 365-class · YOLOv8-X Config (MMYOLO 0.6+)   2025-06-27
################################################################################

# ────── A. 데이터 경로 ───────────────────────────────────────────────
DATA_ROOT   = '/'
TRAIN_JSON  = '/data/ltb/ultralytics/objects365_mmyolo_coco/train_coco_fix.json'
VAL_JSON    = '/data/ltb/ultralytics/objects365_mmyolo_coco/val_coco_fix.json'
LOAD_FROM   = None

# 사전 학습 weights 로딩 (없으면 None)
load_from = LOAD_FROM

# ────── B. 학습 관련 ───────────────────────────────────────────────
EPOCHS              = 100
SWITCH_EPOCH        = 80
CLOSE_MOSAIC_EPOCHS = 10

# ────── C. 배치 사이즈 ─────────────────────────────────────────────
TRAIN_BATCH_SIZE = 8
VAL_BATCH_SIZE   = 8
TEST_BATCH_SIZE  = 8

# [D] 클래스 관련
CLASS_NAMES   = (
    #   1 –  20
    "Person", "Sneakers", "Chair", "Other Shoes", "Hat", "Car", "Lamp",
    "Glasses", "Bottle", "Desk", "Cup", "Street Lights", "Cabinet/Shelf",
    "Handbag/Satchel", "Bracelet", "Plate", "Picture/Frame", "Helmet",
    "Book", "Gloves",
    #  21 –  40
    "Storage box", "Boat", "Leather Shoes", "Flower", "Bench",
    "Potted Plant", "Bowl/Basin", "Flag", "Pillow", "Boots",
    "Vase", "Microphone", "Necklace", "Ring", "SUV", "Wine Glass",
    "Belt", "Moniter/TV", "Backpack", "Umbrella",
    #  41 –  60
    "Traffic Light", "Speaker", "Watch", "Tie", "Trash bin Can",
    "Slippers", "Bicycle", "Stool", "Barrel/Bucket", "Van", "Couch",
    "Sandals", "Basket", "Drum", "Pen/Pencil", "Bus", "Wild Bird",
    "High Heels", "Motorcycle", "Guitar",
    #  61 –  80
    "Carpet", "Cell Phone", "Bread", "Camera", "Canned",
    "Truck", "Traffic cone", "Cymbal", "Lifesaver", "Towel",
    "Stuffed Toy", "Candle", "Sailboat", "Laptop", "Awning", "Bed",
    "Faucet", "Tent", "Horse", "Mirror",
    #  81 – 100
    "Power outlet", "Sink", "Apple", "Air Conditioner", "Knife",
    "Hockey Stick", "Paddle", "Pickup Truck", "Fork", "Traffic Sign",
    "Ballon", "Tripod", "Dog", "Spoon", "Clock", "Pot", "Cow", "Cake",
    "Dinning Table", "Sheep",
    # 101 – 120
    "Hanger", "Blackboard/Whiteboard", "Napkin", "Other Fish",
    "Orange/Tangerine", "Toiletry", "Keyboard", "Tomato", "Lantern",
    "Machinery Vehicle", "Fan", "Green Vegetables", "Banana",
    "Baseball Glove", "Airplane", "Mouse", "Train", "Pumpkin",
    "Soccer", "Skiboard",
    # 121 – 140
    "Luggage", "Nightstand", "Tea pot", "Telephone", "Trolley",
    "Head Phone", "Sports Car", "Stop Sign", "Dessert", "Scooter",
    "Stroller", "Crane", "Remote", "Refrigerator", "Oven", "Lemon",
    "Duck", "Baseball Bat", "Surveillance Camera", "Cat",
    # 141 – 160
    "Jug", "Broccoli", "Piano", "Pizza", "Elephant", "Skateboard",
    "Surfboard", "Gun", "Skating and Skiing shoes", "Gas stove",
    "Donut", "Bow Tie", "Carrot", "Toilet", "Kite", "Strawberry",
    "Other Balls", "Shovel", "Pepper", "Computer Box",
    # 161 – 180
    "Toilet Paper", "Cleaning Products", "Chopsticks", "Microwave",
    "Pigeon", "Baseball", "Cutting/Chopping Board", "Coffee Table",
    "Side Table", "Scissors", "Marker", "Pie", "Ladder", "Snowboard",
    "Cookies", "Radiator", "Fire Hydrant", "Basketball", "Zebra",
    "Grape",
    # 181 – 200
    "Giraffe", "Potato", "Sausage", "Tricycle", "Violin", "Egg",
    "Fire Extinguisher", "Candy", "Fire Truck", "Billards", "Converter",
    "Bathtub", "Wheelchair", "Golf Club", "Briefcase", "Cucumber",
    "Cigar/Cigarette", "Paint Brush", "Pear", "Heavy Truck",
    # 201 – 220
    "Hamburger", "Extractor", "Extention Cord", "Tong",
    "Tennis Racket", "Folder", "American Football", "Earphone",
    "Mask", "Kettle", "Tennis", "Ship", "Swing", "Coffee Machine",
    "Slide", "Carriage", "Onion", "Green beans", "Projector",
    "Frisbee",
    # 221 – 240
    "Washing Machine/Drying Machine", "Chicken", "Printer",
    "Watermelon", "Saxophone", "Tissue", "Toothbrush",
    "Ice cream", "Hotair Ballon", "Cello", "French Fries", "Scale",
    "Trophy", "Cabbage", "Hot dog", "Blender", "Peach", "Rice",
    "Wallet/Purse", "Volleyball",
    # 241 – 260
    "Deer", "Goose", "Tape", "Tablet", "Cosmetics", "Trumpet",
    "Pineapple", "Golf Ball", "Ambulance", "Parking meter", "Mango",
    "Key", "Hurdle", "Fishing Rod", "Medal", "Flute", "Brush",
    "Penguin", "Megaphone", "Corn",
    # 261 – 280
    "Lettuce", "Garlic", "Swan", "Helicopter", "Green Onion",
    "Sandwich", "Nuts", "Speed Limit Sign", "Induction Cooker",
    "Broom", "Trombone", "Plum", "Rickshaw", "Goldfish", "Kiwi Fruit",
    "Router/Modem", "Poker Card", "Toaster", "Shrimp", "Sushi",
    # 281 – 300
    "Cheese", "Notepaper", "Cherry", "Pliers", "CD", "Pasta",
    "Hammer", "Cue", "Avocado", "Hamimelon", "Flask", "Mushroom",
    "Screwdriver", "Soap", "Recorder", "Bear", "Eggplant",
    "Board Eraser", "Coconut", "Tape Measure/Ruler",
    # 301 – 320
    "Pig", "Showerhead", "Globe", "Chips", "Steak", "Crosswalk Sign",
    "Stapler", "Camel", "Formula 1", "Pomegranate", "Dishwasher",
    "Crab", "Hoverboard", "Meat Ball", "Rice Cooker", "Tuba",
    "Calculator", "Papaya", "Antelope", "Parrot",
    # 321 – 340
    "Seal", "Butterfly", "Dumbbell", "Donkey", "Lion", "Urinal",
    "Dolphin", "Electric Drill", "Hair Dryer", "Egg Tart", "Jellyfish",
    "Treadmill", "Lighter", "Grapefruit", "Game Board", "Mop",
    "Radish", "Baozi", "Target", "French",
    # 341 – 360
    "Spring Rolls", "Monkey", "Rabbit", "Pencil Case", "Yak",
    "Red Cabbage", "Binoculars", "Asparagus", "Barbell", "Scallop",
    "Noodles", "Comb", "Dumpling", "Oyster", "Table Tennis Paddle",
    "Cosmetics Brush/Eyeliner Pencil", "Chainsaw", "Eraser", "Lobster",
    "Durian",
    # 361 – 365
    "Okra", "Lipstick", "Cosmetics Mirror", "Curling", "Table Tennis"
    )
NUM_CLASSES   = len(CLASS_NAMES)
CLASS_WEIGHTS = [1.0] * NUM_CLASSES




# ────── D. 하이퍼파라미터 ───────────────────────────────────────────
BASE_LR      = 0.00125
MIXUP_PROB   = 0.15
WEIGHT_DECAY = 0.0005
MOMENTUM     = 0.937
ALBU_PROB    = 0.01

PALETTE = [
    (108, 60, 213),
    (127, 52, 97),
    (20, 41, 86),
    (180, 8, 173),
    (146, 110, 40),
    (225, 199, 203),
    (74, 47, 14),
    (212, 27, 157),
    (94, 154, 73),
    (230, 212, 62),
    (114, 131, 10),
    (45, 235, 253),
    (113, 65, 58),
    (118, 20, 133),
    (171, 134, 253),
    (14, 61, 58),
    (193, 185, 229),
    (27, 49, 35),
    (108, 236, 111),
    (92, 103, 75),
    (174, 145, 208),
    (232, 150, 1),
    (254, 58, 200),
    (12, 195, 158),
    (58, 156, 147),
    (94, 208, 44),
    (82, 80, 199),
    (152, 5, 187),
    (65, 29, 99),
    (132, 67, 171),
    (108, 22, 250),
    (40, 211, 158),
    (99, 128, 197),
    (63, 249, 37),
    (36, 185, 18),
    (249, 193, 187),
    (120, 94, 151),
    (222, 123, 74),
    (125, 139, 79),
    (137, 125, 134),
    (97, 28, 121),
    (84, 77, 13),
    (22, 136, 25),
    (5, 121, 254),
    (49, 88, 180),
    (233, 56, 96),
    (65, 144, 2),
    (156, 235, 35),
    (38, 80, 152),
    (130, 76, 81),
    (109, 9, 47),
    (129, 245, 11),
    (198, 206, 16),
    (188, 196, 158),
    (175, 200, 11),
    (222, 148, 248),
    (68, 81, 157),
    (176, 172, 27),
    (239, 107, 193),
    (125, 220, 10),
    (36, 158, 184),
    (169, 61, 137),
    (69, 231, 106),
    (82, 120, 0),
    (252, 209, 137),
    (87, 89, 118),
    (42, 38, 75),
    (234, 55, 174),
    (21, 58, 183),
    (53, 140, 104),
    (111, 100, 234),
    (21, 164, 102),
    (212, 54, 240),
    (184, 229, 4),
    (220, 214, 11),
    (203, 179, 11),
    (126, 120, 186),
    (250, 178, 89),
    (49, 153, 202),
    (199, 144, 53),
    (244, 246, 150),
    (170, 14, 82),
    (96, 96, 238),
    (145, 120, 219),
    (29, 133, 111),
    (201, 157, 71),
    (65, 47, 159),
    (69, 196, 166),
    (177, 239, 223),
    (27, 218, 72),
    (164, 142, 220),
    (193, 118, 237),
    (221, 143, 10),
    (231, 241, 229),
    (210, 119, 253),
    (48, 164, 70),
    (101, 223, 161),
    (217, 205, 133),
    (98, 42, 105),
    (237, 79, 163),
    (0, 180, 174),
    (136, 5, 67),
    (80, 197, 68),
    (182, 124, 138),
    (67, 130, 86),
    (24, 111, 142),
    (121, 193, 125),
    (176, 147, 162),
    (58, 199, 73),
    (231, 215, 96),
    (17, 247, 162),
    (106, 151, 134),
    (43, 87, 86),
    (167, 252, 221),
    (143, 49, 232),
    (102, 60, 158),
    (92, 112, 193),
    (242, 147, 204),
    (63, 59, 152),
    (218, 61, 159),
    (21, 204, 18),
    (101, 28, 34),
    (33, 20, 134),
    (37, 103, 131),
    (26, 221, 94),
    (21, 33, 154),
    (26, 76, 242),
    (136, 43, 5),
    (230, 141, 201),
    (226, 206, 38),
    (70, 30, 226),
    (224, 114, 251),
    (249, 62, 88),
    (53, 162, 176),
    (81, 246, 54),
    (117, 170, 84),
    (4, 139, 93),
    (95, 195, 159),
    (118, 89, 18),
    (112, 76, 184),
    (80, 140, 41),
    (82, 77, 176),
    (183, 185, 3),
    (164, 216, 9),
    (54, 236, 86),
    (116, 220, 108),
    (177, 226, 215),
    (189, 181, 108),
    (112, 143, 165),
    (7, 239, 102),
    (141, 192, 158),
    (146, 76, 161),
    (2, 3, 7),
    (189, 164, 106),
    (231, 59, 99),
    (18, 183, 194),
    (231, 94, 136),
    (111, 164, 24),
    (190, 66, 2),
    (189, 185, 6),
    (157, 16, 221),
    (216, 211, 96),
    (74, 183, 186),
    (15, 36, 162),
    (55, 252, 169),
    (113, 94, 38),
    (12, 55, 136),
    (170, 108, 141),
    (251, 27, 25),
    (246, 27, 181),
    (179, 109, 230),
    (180, 170, 222),
    (246, 12, 72),
    (44, 119, 64),
    (211, 47, 105),
    (45, 184, 78),
    (242, 189, 99),
    (93, 138, 15),
    (52, 134, 59),
    (112, 55, 34),
    (12, 24, 247),
    (147, 80, 38),
    (158, 229, 60),
    (187, 142, 137),
    (198, 136, 161),
    (128, 72, 171),
    (221, 152, 98),
    (20, 116, 173),
    (10, 81, 36),
    (228, 152, 159),
    (57, 85, 253),
    (52, 115, 159),
    (243, 229, 9),
    (147, 186, 7),
    (102, 183, 242),
    (252, 36, 137),
    (62, 65, 116),
    (142, 7, 194),
    (193, 131, 3),
    (4, 155, 56),
    (119, 136, 103),
    (187, 104, 26),
    (173, 40, 5),
    (108, 253, 187),
    (48, 91, 104),
    (249, 148, 106),
    (40, 74, 235),
    (27, 100, 30),
    (155, 0, 243),
    (69, 128, 141),
    (231, 85, 222),
    (124, 229, 212),
    (80, 187, 192),
    (29, 245, 95),
    (22, 27, 117),
    (170, 230, 186),
    (25, 57, 27),
    (22, 154, 7),
    (110, 195, 251),
    (154, 181, 143),
    (92, 51, 198),
    (8, 46, 56),
    (56, 160, 145),
    (156, 14, 7),
    (6, 143, 204),
    (191, 32, 59),
    (69, 58, 165),
    (145, 77, 115),
    (30, 19, 114),
    (133, 156, 219),
    (136, 143, 33),
    (184, 103, 243),
    (85, 105, 44),
    (69, 194, 203),
    (12, 221, 133),
    (208, 132, 154),
    (228, 163, 61),
    (65, 32, 112),
    (87, 70, 108),
    (71, 184, 164),
    (233, 71, 37),
    (29, 154, 166),
    (246, 69, 242),
    (18, 125, 191),
    (244, 1, 153),
    (137, 203, 207),
    (224, 8, 247),
    (33, 224, 78),
    (186, 24, 183),
    (29, 153, 222),
    (241, 221, 180),
    (189, 147, 211),
    (128, 71, 150),
    (33, 43, 147),
    (139, 216, 81),
    (112, 240, 163),
    (135, 220, 186),
    (19, 127, 3),
    (172, 105, 100),
    (179, 93, 110),
    (43, 198, 187),
    (140, 230, 30),
    (104, 221, 246),
    (195, 20, 61),
    (163, 113, 230),
    (243, 77, 43),
    (145, 53, 254),
    (86, 82, 220),
    (220, 161, 227),
    (177, 134, 101),
    (181, 123, 41),
    (213, 29, 215),
    (124, 42, 242),
    (53, 6, 206),
    (124, 245, 97),
    (186, 243, 247),
    (177, 119, 209),
    (114, 160, 55),
    (65, 232, 37),
    (134, 5, 74),
    (68, 151, 122),
    (93, 219, 81),
    (16, 151, 170),
    (17, 107, 195),
    (70, 83, 176),
    (88, 152, 215),
    (59, 181, 45),
    (120, 90, 193),
    (235, 247, 99),
    (253, 74, 168),
    (221, 44, 222),
    (96, 162, 95),
    (45, 221, 18),
    (213, 171, 104),
    (175, 173, 180),
    (119, 123, 169),
    (167, 131, 74),
    (50, 79, 99),
    (209, 151, 25),
    (20, 73, 108),
    (3, 250, 209),
    (145, 246, 36),
    (57, 230, 210),
    (229, 108, 123),
    (115, 11, 212),
    (142, 16, 76),
    (80, 33, 21),
    (26, 54, 81),
    (165, 189, 232),
    (111, 238, 1),
    (162, 137, 84),
    (108, 237, 209),
    (193, 11, 112),
    (10, 130, 8),
    (95, 114, 198),
    (40, 212, 195),
    (64, 24, 239),
    (105, 2, 4),
    (122, 150, 72),
    (37, 226, 68),
    (30, 118, 31),
    (225, 251, 113),
    (240, 82, 122),
    (64, 58, 125),
    (252, 151, 108),
    (212, 27, 233),
    (84, 113, 26),
    (5, 41, 248),
    (250, 5, 156),
    (65, 203, 79),
    (197, 125, 209),
    (248, 199, 43),
    (133, 202, 63),
    (155, 42, 6),
    (64, 163, 108),
    (161, 250, 44),
    (82, 2, 162),
    (231, 137, 206),
    (199, 188, 206),
    (132, 70, 180),
    (12, 137, 244),
    (215, 130, 76),
    (95, 144, 130),
    (90, 51, 25),
    (210, 9, 144),
    (73, 60, 251),
    (220, 233, 15),
    (15, 166, 219),
    (90, 172, 198),
    (76, 156, 252),
    (185, 202, 150),
    (158, 244, 177),
    (151, 136, 3),
    (253, 253, 252),
    (131, 152, 241),
    (97, 159, 190),
    (159, 38, 176),
    (114, 30, 79),
    (169, 142, 246),
    (7, 79, 105),
    (134, 141, 103),
    (68, 241, 10),
    (70, 247, 168),
    (195, 184, 125),
    (37, 230, 130),
]



# ────── E. DataLoader 워커 수 ───────────────────────────────────────
NUM_WORKERS_TRAIN = 8
NUM_WORKERS_VAL   = 8
NUM_WORKERS_TEST  = 8



# =============================================================================
# 아래부터는 위 전역변수를 사용하여 전체 config를 구성합니다.
# =============================================================================

_backend_args = None

# ----- TTA (Test Time Augmentation) 위한 다양한 해상도 transform 설정 -----
_multiscale_resize_transforms = [
    dict(
        type='Compose',
        transforms=[
            dict(type='YOLOv5KeepRatioResize', scale=(640, 640)),
            dict(
                type='LetterResize',
                scale=(640, 640),
                allow_scale_up=False,
                pad_val=dict(img=114)
            ),
        ]
    ),
    dict(
        type='Compose',
        transforms=[
            dict(type='YOLOv5KeepRatioResize', scale=(320, 320)),
            dict(
                type='LetterResize',
                scale=(320, 320),
                allow_scale_up=False,
                pad_val=dict(img=114)
            ),
        ]
    ),
    dict(
        type='Compose',
        transforms=[
            dict(type='YOLOv5KeepRatioResize', scale=(960, 960)),
            dict(
                type='LetterResize',
                scale=(960, 960),
                allow_scale_up=False,
                pad_val=dict(img=114)
            ),
        ]
    ),
]

affine_scale = 0.9
albu_train_transforms = [
    dict(type='Blur', p=ALBU_PROB),
    dict(type='MedianBlur', p=ALBU_PROB),
    dict(type='ToGray', p=ALBU_PROB),
    dict(type='CLAHE', p=ALBU_PROB),
]
backend_args = None

base_lr = BASE_LR
batch_shapes_cfg = None

# ----- 클래스 이름과 팔레트 정의 (loss, 시각화 등에서 사용) -----
class_name = CLASS_NAMES
close_mosaic_epochs = CLOSE_MOSAIC_EPOCHS


# ----- custom_hooks: EMA, PipelineSwitchHook 등 -----
custom_hooks = [
    dict(
        type='EMAHook',
        ema_type='ExpMomentumEMA',
        momentum=0.0001,
        update_buffers=True,
        priority=49,
        strict_load=False
    ),
    dict(
        type='mmdet.PipelineSwitchHook',
        switch_epoch=SWITCH_EPOCH,  # 전역변수 사용
        switch_pipeline=[
            # 19epoch 이후에 사용할 파이프라인 예시
            dict(type='LoadImageFromFile', backend_args=None),
            dict(type='LoadAnnotations', with_bbox=True),
            dict(type='YOLOv5KeepRatioResize', scale=(960, 960)),
            dict(
                type='LetterResize',
                scale=(960, 960),
                allow_scale_up=True,
                pad_val=dict(img=114.0)
            ),
            dict(
                type='YOLOv5RandomAffine',
                max_rotate_degree=10.0,
                max_shear_degree=2.0,
                scaling_ratio_range=(0.8, 1.2),
                border_val=(114, 114, 114),
                max_aspect_ratio=100
            ),
            dict(
                type='mmdet.Albu',
                transforms=[
                    dict(type='Blur', p=ALBU_PROB),
                    dict(type='MedianBlur', p=ALBU_PROB),
                    dict(type='ToGray', p=ALBU_PROB),
                    dict(type='CLAHE', p=ALBU_PROB),
                ],
                bbox_params=dict(
                    format='pascal_voc',
                    label_fields=['gt_bboxes_labels', 'gt_ignore_flags'],
                    type='BboxParams'
                ),
                keymap=dict(img='image', gt_bboxes='bboxes'),
            ),
            dict(type='YOLOv5HSVRandomAug'),
            dict(type='mmdet.RandomFlip', prob=0.5),
            dict(
                type='mmdet.PackDetInputs',
                meta_keys=(
                    'img_id', 'img_path', 'ori_shape', 'img_shape',
                    'flip', 'flip_direction'
                )
            ),
        ]
    ),
]

data_root = DATA_ROOT  # 전역변수 적용
dataset_type = 'YOLOv5CocoDataset'
deepen_factor = 1.0

default_hooks = dict(
    checkpoint=dict(
        type='CheckpointHook',
        interval=1,
        max_keep_ckpts=10,
        save_best='auto'
    ),
    logger=dict(type='LoggerHook', interval=50),
    param_scheduler=dict(
        type='YOLOv5ParamSchedulerHook',
        scheduler_type='linear',
        max_epochs=EPOCHS,  # 전역변수 사용
        lr_factor=0.01
    ),
    sampler_seed=dict(type='DistSamplerSeedHook'),
    timer=dict(type='IterTimerHook'),
    visualization=dict(type='mmdet.DetVisualizationHook')
)

default_scope = 'mmyolo'

env_cfg = dict(
    cudnn_benchmark=True,
    dist_cfg=dict(backend='nccl'),
    mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0)
)

img_scale = (640, 640)
img_scales = [(640, 640), (320, 320), (960, 960)]
last_stage_out_channels = 512


last_transform = [
    dict(
        type='mmdet.Albu',
        transforms=[
            dict(type='Blur', p=ALBU_PROB),
            dict(type='MedianBlur', p=ALBU_PROB),
            dict(type='ToGray', p=ALBU_PROB),
            dict(type='CLAHE', p=ALBU_PROB),
        ],
        bbox_params=dict(
            format='pascal_voc',
            label_fields=['gt_bboxes_labels', 'gt_ignore_flags'],
            type='BboxParams'
        ),
        keymap=dict(img='image', gt_bboxes='bboxes'),
    ),
    dict(type='YOLOv5HSVRandomAug'),
    dict(type='mmdet.RandomFlip', prob=0.5),
    dict(
        type='mmdet.PackDetInputs',
        meta_keys=(
            'img_id', 'img_path', 'ori_shape', 'img_shape',
            'scale_factor', 'pad_param'
        )
    ),
]

launcher = 'none'

load_config = dict(
    ignore_keys=[
        'bbox_head.head_module.cls_preds.0.weight',
        'bbox_head.head_module.cls_preds.0.bias',
        'bbox_head.head_module.cls_preds.1.weight',
        'bbox_head.head_module.cls_preds.1.bias',
        'bbox_head.head_module.cls_preds.2.weight',
        'bbox_head.head_module.cls_preds.2.bias',
    ],
    strict=False
)

load_from = LOAD_FROM
log_level = 'INFO'
log_processor = dict(type='LogProcessor', by_epoch=True, window_size=50)

loss_bbox_weight = 7.5
loss_cls_weight  = 1.0
loss_dfl_weight  = 0.375
lr_factor        = 0.01
max_aspect_ratio = 100
max_epochs       = EPOCHS   # 전역변수 사용
max_keep_ckpts   = 2

metainfo = dict(
    classes=CLASS_NAMES,  # 전역변수 적용
    palette=PALETTE       # 전역변수 적용
)

mixup_prob = MIXUP_PROB


# ────── H. Model ────────────────────────────────────────────────────────
model = dict(
    type='YOLODetector',
    data_preprocessor=dict(
        type='YOLOv5DetDataPreprocessor',
        bgr_to_rgb=True,
        mean=[0.0, 0.0, 0.0],
        std=[255.0, 255.0, 255.0]
    ),
    backbone=dict(
        type='YOLOv8CSPDarknet',
        arch='P5',
        deepen_factor=deepen_factor,
        widen_factor=1.0,
        norm_cfg=dict(type='BN', momentum=0.03, eps=0.001),
        act_cfg=dict(type='SiLU', inplace=True),
        last_stage_out_channels=512
    ),
    neck=dict(
        type='YOLOv8PAFPN',
        deepen_factor=deepen_factor,
        widen_factor=1.0,
        in_channels=[256, 512, 512],
        out_channels=[256, 512, 512],
        num_csp_blocks=3,
        norm_cfg=dict(type='BN', momentum=0.03, eps=0.001),
        act_cfg=dict(type='SiLU', inplace=True)
    ),
    bbox_head=dict(
        type='YOLOv8Head',
        bbox_coder=dict(type='DistancePointBBoxCoder'),
        head_module=dict(
            type='YOLOv8HeadModule',
            num_classes=NUM_CLASSES,  # 전역변수 사용
            in_channels=[256, 512, 512],
            featmap_strides=[8, 16, 32],
            reg_max=16,
            act_cfg=dict(type='SiLU', inplace=True),
            norm_cfg=dict(type='BN', momentum=0.03, eps=0.001)
        ),
        loss_bbox=dict(
            type='IoULoss',
            bbox_format='xywh',
            iou_mode='ciou',
            loss_weight=loss_bbox_weight,
            return_iou=False,
            reduction='sum'
        ),
        loss_cls=dict(
            type='CrossEntropyCustomLoss_mmyolo',
            use_sigmoid=True,
            num_classes=NUM_CLASSES,
            reduction='none',
            loss_weight=0.75,
            class_weight=CLASS_WEIGHTS  # 전역변수 사용
        ),
        loss_dfl=dict(
            type='mmdet.DistributionFocalLoss',
            reduction='mean',
            loss_weight=loss_dfl_weight
        ),
        prior_generator=dict(
            type='mmdet.MlvlPointGenerator',
            strides=[8, 16, 32],
            offset=0.5
        )
    ),
    train_cfg=dict(
        assigner=dict(
            type='BatchTaskAlignedAssigner',
            num_classes=NUM_CLASSES,
            topk=10,
            alpha=0.5,
            beta=6.0,
            eps=1e-09,
            use_ciou=True
        )
    ),
    test_cfg=dict(
        nms=dict(type='nms', iou_threshold=0.5),
        nms_pre=30000,
        score_thr=0.01,
        max_per_img=300,
        multi_label=True
    )
)

model_test_cfg = dict(
    nms=dict(type='nms', iou_threshold=0.5),
    nms_pre=30000,
    score_thr=0.01,
    max_per_img=500,
    multi_label=True
)

# ----- Mosaic transform 설정 예시 -----
mosaic_affine_transform = [
    dict(
        type='Mosaic',
        img_scale=(640, 640),
        pad_val=114.0,
        pre_transform=[
            dict(type='LoadImageFromFile', backend_args=None),
            dict(type='LoadAnnotations', with_bbox=True)
        ]
    ),
    dict(
        type='YOLOv5RandomAffine',
        border=(-320, -320),
        border_val=(114, 114, 114),
        max_aspect_ratio=100,
        max_rotate_degree=0.0,
        max_shear_degree=0.0,
        scaling_ratio_range=(0.1, 1.9)
    ),
]

mosaic_affine_transform_960 = [
    dict(
        type='Mosaic',
        img_scale=(960, 960),
        pad_val=114.0,
        pre_transform=[
            dict(type='LoadImageFromFile', backend_args=None),
            dict(type='LoadAnnotations', with_bbox=True)
        ]
    ),
    dict(
        type='YOLOv5RandomAffine',
        border=(-480, -480),
        border_val=(114, 114, 114),
        max_aspect_ratio=100,
        max_rotate_degree=0.0,
        max_shear_degree=0.0,
        scaling_ratio_range=(0.1, 1.9)
    ),
]

norm_cfg = dict(type='BN', momentum=0.03, eps=0.001)
num_classes = NUM_CLASSES
num_det_layers = 3


# ────── G. Optimizer & Scheduler ───────────────────────────────────────
optim_wrapper = dict(
    type='AmpOptimWrapper',
    constructor='YOLOv5OptimizerConstructor',
    loss_scale='dynamic',
    optimizer=dict(
        type='SGD',
        lr=BASE_LR,
        momentum=MOMENTUM,
        nesterov=True,
        weight_decay=WEIGHT_DECAY,
        batch_size_per_gpu=TRAIN_BATCH_SIZE
    ),
    clip_grad=dict(max_norm=5.0)
)

param_scheduler = None



pre_transform = [
    dict(type='LoadImageFromFile', backend_args=None),
    dict(type='LoadAnnotations', with_bbox=True),
]

resume = False
save_epoch_intervals = 1
strides = [8, 16, 32]

tal_alpha = 0.5
tal_beta = 6.0
tal_topk = 10

test_cfg = dict(type='TestLoop')
test_dataloader = dict(
    batch_size=TEST_BATCH_SIZE,
    num_workers=NUM_WORKERS_TEST,
    persistent_workers=False,
    pin_memory=True,
    sampler=dict(type='DefaultSampler', shuffle=False),
    dataset=dict(
        type='YOLOv5CocoDataset',
        data_root=DATA_ROOT,
        ann_file=VAL_JSON,
        data_prefix=dict(img=''),
        metainfo=dict(
            classes=CLASS_NAMES,
            palette=PALETTE
        ),
        pipeline=[
            dict(type='LoadImageFromFile'),
            dict(type='YOLOv5KeepRatioResize', scale=(960, 960)),
            dict(
                type='LetterResize',
                scale=(960, 960),
                allow_scale_up=False,
                pad_val=dict(img=114)
            ),
            # dict(type='mmdet.LoadAnnotations', with_bbox=True),
            dict(
                type='mmdet.PackDetInputs',
                meta_keys=(
                    'img_id', 'img_path', 'ori_shape', 'img_shape',
                    'scale_factor', 'pad_param'
                )
            ),
        ],
        test_mode=True,
        batch_shapes_cfg=None
    )
)

test_evaluator = dict(
    type='mmdet.CocoMetric',
    ann_file= VAL_JSON,
    metric='bbox',
    proposal_nums=(100, 200, 500)
)

test_pipeline = [
    dict(type='LoadImageFromFile', backend_args=None),
    dict(type='YOLOv5KeepRatioResize', scale=(640, 640)),
    dict(
        type='LetterResize',
        scale=(640, 640),
        allow_scale_up=False,
        pad_val=dict(img=114)
    ),
    # dict(type='mmdet.LoadAnnotations', with_bbox=True),
    dict(
        type='mmdet.PackDetInputs',
        meta_keys=(
            'img_id', 'img_path', 'ori_shape', 'img_shape',
            'scale_factor', 'pad_param'
        )
    ),
]

train_ann_file = TRAIN_JSON
train_batch_size_per_gpu = TRAIN_BATCH_SIZE



train_cfg = dict(
    type='EpochBasedTrainLoop',
    max_epochs=EPOCHS,
    val_interval=1,
    dynamic_intervals=[(499, 1)]
)

train_data_prefix = ''
train_dataloader = dict(
    batch_size=TRAIN_BATCH_SIZE,
    num_workers=NUM_WORKERS_TRAIN,
    persistent_workers=False,
    pin_memory=True,
    sampler=dict(type='DefaultSampler', shuffle=True),
    collate_fn=dict(type='yolov5_collate'),
    dataset=dict(
        type='RepeatDataset',
        times=1,  # 원본+회전캔버스 데이터 반복 횟수 (데이터 균형 조절)
        dataset=dict(
            type='YOLOv5CocoDataset',
            data_root=DATA_ROOT,
            ann_file=TRAIN_JSON,
            data_prefix=dict(img=''),
            filter_cfg=dict(filter_empty_gt=False, min_size=32),
            metainfo=dict(
                classes=CLASS_NAMES,
                palette=PALETTE
            ),
            pipeline=[
                dict(type='LoadImageFromFile', backend_args=None),
                dict(type='LoadAnnotations', with_bbox=True),
                dict(
                    type='YOLOv5RandomAffine',
                    max_rotate_degree=10.0,
                    max_shear_degree=2.0,
                    scaling_ratio_range=(0.8, 1.2),
                    border_val=(114, 114, 114)
                ),
                dict(
                    type='Mosaic',
                    img_scale=(960, 960),
                    pad_val=114.0,
                    pre_transform=[
                        dict(type='LoadImageFromFile', backend_args=None),
                        dict(type='LoadAnnotations', with_bbox=True),
                    ]
                ),
                dict(
                    type='YOLOv5RandomAffine',
                    border=(-480, -480),
                    border_val=(114, 114, 114),
                    max_aspect_ratio=100,
                    max_rotate_degree=0.0,
                    max_shear_degree=0.0,
                    scaling_ratio_range=(0.1, 1.9)
                ),
                dict(
                    type='mmdet.Albu',
                    transforms=[
                        dict(type='Blur', p=ALBU_PROB),
                        dict(type='MedianBlur', p=ALBU_PROB),
                        dict(type='ToGray', p=ALBU_PROB),
                        dict(type='CLAHE', p=ALBU_PROB),
                    ],
                    bbox_params=dict(
                        type='BboxParams',
                        format='pascal_voc',
                        label_fields=['gt_bboxes_labels', 'gt_ignore_flags']
                    ),
                    keymap=dict(img='image', gt_bboxes='bboxes')
                ),
                dict(type='YOLOv5HSVRandomAug'),
                dict(type='mmdet.RandomFlip', prob=0.5),
                dict(
                    type='mmdet.PackDetInputs',
                    meta_keys=(
                        'img_id', 'img_path', 'ori_shape', 'img_shape',
                        'flip', 'flip_direction'
                    )
                ),
            ],
        )
    ),
)

train_num_workers = NUM_WORKERS_TRAIN
train_pipeline = [
    # 학습 시 pipeline은 위와 동일하게 사용합니다.
]

train_pipeline_stage2 = [
    dict(type='LoadImageFromFile', backend_args=None),
    dict(type='LoadAnnotations', with_bbox=True),
    dict(type='YOLOv5KeepRatioResize', scale=(960, 960)),
    dict(
        type='LetterResize',
        scale=(960, 960),
        allow_scale_up=True,
        pad_val=dict(img=114.0)
    ),
    dict(
        type='YOLOv5RandomAffine',
        max_rotate_degree=10.0,
        max_shear_degree=2.0,
        scaling_ratio_range=(0.8, 1.2),
        border_val=(114, 114, 114),
        max_aspect_ratio=100
    ),
    dict(
        type='mmdet.Albu',
        transforms=[
            dict(type='Blur', p=ALBU_PROB),
            dict(type='MedianBlur', p=ALBU_PROB),
            dict(type='ToGray', p=ALBU_PROB),
            dict(type='CLAHE', p=ALBU_PROB),
        ],
        bbox_params=dict(
            type='BboxParams',
            format='pascal_voc',
            label_fields=['gt_bboxes_labels', 'gt_ignore_flags']
        ),
        keymap=dict(img='image', gt_bboxes='bboxes')
    ),
    dict(type='YOLOv5HSVRandomAug'),
    dict(type='mmdet.RandomFlip', prob=0.5),
    dict(
        type='mmdet.PackDetInputs',
        meta_keys=(
            'img_id', 'img_path', 'ori_shape', 'img_shape',
            'flip', 'flip_direction'
        )
    ),
]

val_cfg = dict(type='ValLoop')
val_data_prefix = ''
val_dataloader = dict(
    batch_size=VAL_BATCH_SIZE,
    num_workers=NUM_WORKERS_VAL,
    persistent_workers=False,
    pin_memory=True,
    sampler=dict(type='DefaultSampler', shuffle=False),
    dataset=dict(
        type='YOLOv5CocoDataset',
        data_root=DATA_ROOT,
        ann_file=VAL_JSON,
        data_prefix=dict(img=''),
        metainfo=dict(
            classes=CLASS_NAMES,
            palette=PALETTE
        ),
        pipeline=[
            dict(type='LoadImageFromFile'),
            dict(type='YOLOv5KeepRatioResize', scale=(960, 960)),
            dict(
                type='LetterResize',
                scale=(960, 960),
                allow_scale_up=False,
                pad_val=dict(img=114)
            ),
            # dict(type='mmdet.LoadAnnotations', with_bbox=True),
            dict(
                type='mmdet.PackDetInputs',
                meta_keys=(
                    'img_id', 'img_path', 'ori_shape', 'img_shape',
                    'scale_factor', 'pad_param'
                )
            ),
        ],
        test_mode=True,
        batch_shapes_cfg=None
    )
)

val_evaluator = dict(
    type='mmdet.CocoMetric',
    ann_file= VAL_JSON,
    metric='bbox',
    proposal_nums=(100, 200, 500)
)

val_interval_stage2 = 1
val_num_workers = NUM_WORKERS_VAL

vis_backends = [dict(type='LocalVisBackend')]
visualizer = dict(
    type='mmdet.DetLocalVisualizer',
    name='visualizer',
    vis_backends=[dict(type='LocalVisBackend')]
)

weight_decay = WEIGHT_DECAY
widen_factor = 1.0
work_dir = 'work_dirs/ff_base_cutmix_combined_1,2,5_01'

tta_model = dict(
    type='mmdet.DetTTAModel',
    tta_cfg=dict(
        nms=dict(type='nms', iou_threshold=0.65),
        max_per_img=300
    )
)

tta_pipeline = [
    dict(type='LoadImageFromFile', backend_args=None),
    dict(
        type='TestTimeAug',
        transforms=[
            [
                dict(
                    type='Compose',
                    transforms=[
                        dict(type='YOLOv5KeepRatioResize', scale=(640, 640)),
                        dict(
                            type='LetterResize',
                            scale=(640, 640),
                            allow_scale_up=False,
                            pad_val=dict(img=114)
                        )
                    ]
                ),
                dict(
                    type='Compose',
                    transforms=[
                        dict(type='YOLOv5KeepRatioResize', scale=(320, 320)),
                        dict(
                            type='LetterResize',
                            scale=(320, 320),
                            allow_scale_up=False,
                            pad_val=dict(img=114)
                        )
                    ]
                ),
                dict(
                    type='Compose',
                    transforms=[
                        dict(type='YOLOv5KeepRatioResize', scale=(960, 960)),
                        dict(
                            type='LetterResize',
                            scale=(960, 960),
                            allow_scale_up=False,
                            pad_val=dict(img=114)
                        )
                    ]
                ),
            ],
            [
                dict(type='mmdet.RandomFlip', prob=1.0),
                dict(type='mmdet.RandomFlip', prob=0.0),
            ],
            [
                dict(type='mmdet.LoadAnnotations', with_bbox=True),
            ],
            [
                dict(
                    type='mmdet.PackDetInputs',
                    meta_keys=(
                        'img_id', 'img_path', 'ori_shape', 'img_shape',
                        'scale_factor', 'pad_param', 'flip', 'flip_direction'
                    )
                ),
            ],
        ]
    ),
]



